The view shifts after the keyboard is finished loading, how to I get the view to shift at the same time - swift

When I call my keyboard it will slide up into place, then the rest of the view will slide up to make room for it. Similarly when I dismiss the keyboard, the keyboard will slide out, then the rest of the view will slide down. Is it possible to have the keyboard and the view to slide at the same time?
Here is the code I currently have
// MARK: Animated Keyboard
#objc func keyboardWillShow(notification: NSNotification) {
// check to see if a keyboard exists
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
print("Show")
// Check to see if the information for the size of the keyboard exists
guard let userInfo = notification.userInfo
else {return}
// get the size of the keyboard
guard let keyboardSize = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
else {return}
// get the duration of the keyboard transition
let duration = notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
// get the type and speed of the keyboard transition
let curve = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! UInt
// move the frame by the size of the keyboard
let keyboardFrame = keyboardSize.cgRectValue
UIView.animateKeyframes(withDuration: duration, delay: -0.2, options: UIView.KeyframeAnimationOptions(rawValue: curve), animations: {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardFrame.height
}
}, completion: nil)
}
}
This is not causing any errors, it compiles and runs without issue. It just looks bad.

From your description is sounds like you have actually registered for keyboardDidShowNotification instead of keyboardWillShowNotification.
Make sure you have registered for the correct notification.
You should also use the UIView.animate method and not UIView.animateKeyframes. This of course also means you need to replace UIView.KeyframeAnimationOptions with UIView.AnimationOptions.
Lastly, don't attempt to use a negative delay. UIKit has a lot of power but time travel isn't one of them.

Related

How to animate display of same View Controller but with updated content

I want to animate an update to a sceen's content as if transitioning to a different View Controller. For example, imagine a scene of a calendar page with today's events. A swipe to the left updates the content to tomorrow's events but the View Controller and scene are the same. Still, I'd like to animate the new content sliding in from the right.
I've tried animating moving the entire view to the left. And that works, but it reveals a black screen beneath, which although quickly replaced with the correct content is nevertheless jarring.
I remember back in the HyperCard days you could lock the display of a card, update the contents, then unlock the display with a transition, say, a dissolve or wipe. I understand that is probably not how iOS does things, but I mention it just to help describe what I'm trying to do.
Adding code that does gesture recognition and move...
#objc func swipeDetected(gesture: UIGestureRecognizer) {
let calendar = Calendar.current
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case .left:
// move current page to the left
animateViewMoving(right: false, timeInterval: 0.3)
// update content
currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
populateSchedItems()
self.tableView.reloadData()
// return page with no duration
animateViewMoving(right: true, timeInterval: 0.0)
case .right:
// move page to left with no duration
animateViewMoving(right: false, timeInterval: 0.0)
// update content
currentDate = calendar.date(byAdding: .day, value: -1, to: currentDate)!
populateSchedItems()
self.tableView.reloadData()
// slide new content in from the left
animateViewMoving(right: true, timeInterval: 0.3)
default:
break
}
}
}
func animateViewMoving (toTheRight: Bool, timeInterval: Double) {
let frameWidth = self.view.frame.width
let movementDuration:TimeInterval = timeInterval
let movement:CGFloat = ( toTheRight ? frameWidth : -frameWidth)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration )
self.view.frame = self.view.frame.offsetBy(dx: movement, dy: 0)
UIView.commitAnimations()
}
Take a snapshot view of the current contents and place it behind the current view. (This will give us something other than the black window to look at behind the animation.) Now do exactly what you're already doing: move the real view off to the side and animate it in. Now quietly remove the snapshot view.

Calculate & move UIView on keyboard(show/hide)

I am trying to calculate the position to move a UITextField along with its parent UIView if the keyboard is overlapping the field and move back to its original position after keyboard is closed.
I have already tried https://github.com/hackiftekhar/IQKeyboardManager and it does not work in my particular case.
To explain the problem, please refer two attached screenshot, one when keyboard is opened and another when it is closed.
As you can see, on keyboard open, the text field is overlapping the keyboard, I want to move the text field along with popup view to readjust and sit above the keyboard.
Here is what I tried.
func textFieldDidBeginEditing(_ textField: UITextField) {
self.startOriginY = self.frame.origin.y
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
}
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
let screenHeight = self.backgroundView.frame.height
let viewHeight = self.frame.height
let diffHeight = screenHeight - viewHeight - keyboardHeight
if diffHeight < 0 {
self.frame.origin.y = -self.textField.frame.height
}
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.frame.origin.y = self.startOriginY
}
This code moves the view to incorrect position. I am trying to figure out how to calculate the correct position to move the view and remove keyboard overlap.
What is the best way to go about solving this problem?
Thank you.
Basically you need to embedded your view inside a scroll view and use Apple's example to handle the adjustment by altering the bottom content inset of the scroll view:
Embedded inside a scroll view
Register for keyboard notifications
Implement logic to handle the notifications by altering the bottom content inset
I have converted Apple's code snipe to Swift.
Keyboard will show:
if let info = notification.userInfo,
let size = (info[UIKeyboardFrameEndUserInfoKey] as AnyObject?) {
let newSize = size.cgRectValue.size
let contentInset = UIEdgeInsetsMake(0.0, 0.0, newSize.height, 0.0)
scrollView.contentInset = contentInset
}
Keyboard will hide:
let contentInset = UIEdgeInsets.zero
scrollView.contentInset = contentInset
scrollView.scrollIndicatorInsets = contentInset
You can also add hard-coded offset to the newSize.height i.e: newSize.height + 20

Textfield frame animation is not in sync with keyboard animation duration

I have a text field and I am able to move it up a with keyboard. However, the animation is not in sync. The textfield frame moves up about .5 second ahead of keyboard duration. I think they should be in sync but I'm not able to figure out the problem. Below is my keyboard function animation.
Notification observer:
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: .UIKeyboardWillShow, object: nil)
Animation func:
#objc func handleKeyboardWillShow(notification: NSNotification){
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue, let keyboardDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double, let tabBarHeight = tabBarController?.tabBar.frame.size.height{
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
UIView.animate(withDuration: keyboardDuration, animations: {
self.textfieldBottomeConstraint?.constant = -keyboardHeight + tabBarHeight
self.view.layoutIfNeeded()
})
}
}
I did try this but still get the same results:
self.textfieldBottomeConstraint?.constant = -keyboardHeight + tabBarHeight
UIView.animate(withDuration: keyboardDuration, animations: {
self.view.layoutIfNeeded()
})
Two things to try:
Try using UIKeyboardWillChangeFrame instead of UIKeyboardWillShow
If it does not change anything, try using the keyboard curve inside your animation.
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationCurve(context.animationCurve)
UIView.setAnimationDuration(context.animationDuration)
where curve and duration are
public var animationCurve: UIViewAnimationCurve {
let value = userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
return UIViewAnimationCurve(rawValue: value.intValue)!
}
public var animationDuration: Double {
return userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double
}
The keyboard notification also includes a
UIKeyboardAnimationCurveUserInfoKey and a UIKeyboardAnimationDurationUserInfoKey. You can interrogate those values to make sure your animation uses the same duration and animation curve that the keyboard uses. (Search on "Keyboard Notification User Info Keys" in the Xcode help system to find all the different key/value pairs provided in keyboard notifications.

Extend UIScrollView insets behind keyboard on iOS11

Since Apple introduced safe area insets and adjusted content insets the already working UI layouting code becomes broken. In my case UIScrollView bottom inset extends when the keyboard appears:
func keyboardWillResize(_ notification: Notification) {
let info: [AnyHashable: Any] = notification.userInfo!
let keyboardTop = self.view.frameOnScreen.maxY - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.y
UIView.animate(withDuration: 0.3, animations: {
self.tableView.contentInset.bottom = keyboardTop
self.tableView.scrollIndicatorInsets = self.tableView.contentInset
})
}
Within iOS 11 this code produces extra inset when keyboard appears, equal to tab bar height. It's obvious because now contentInset represents only user-defined insets, and real insets are represented by adjustedContentInset introduced in iOS 11.
So my question is how to deal with this case in good manner? There is option to write
self.tableView.contentInset.bottom = keyboardTop - self.tableView.adjustedContentInset.bottom
but it looks so ugly. Maybe there is built-in method to extend insets behind the keyboard?
Obviously, the answer is in official documentation. Instead of manually adjust content insets, we should delegate this stuff to view controller and deal with it's safe area insets. So, here is working code:
func keyboardWillResize(_ notification: Notification) {
let info: [AnyHashable: Any] = notification.userInfo!
let keyboardTop = self.view.frameOnScreen.maxY - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.y
UIView.animate(withDuration: 0.3, animations: {
self.additionalSafeAreaInsets.bottom = max(keyboardTop - self.view.safeAreaInsets.bottom, 0)
})
}

UITableView Cell display issue

I have a UITableView, its top and bottom distance to superview is 0.
When keyboard appears I update the bottom constraint, so that keyboard will not hide the table. But on updating the bottom constraint, last two or three cells are not completely visible. I am using following code to update the bottom constraint.
func keyboardWillChangeFrameWithNotification(notification: NSNotification, showsKeyboard: Bool) {
let userInfo = notification.userInfo!
let animationDuration: NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
// Convert the keyboard frame from screen to view coordinates.
let keyboardScreenBeginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
if showsKeyboard
{
cntInputViewBottom.constant = keyboardScreenBeginFrame.size.height
}
else
{
cntInputViewBottom.constant = 0
}
}
You are using UIKeyboardFrameBeginUserInfoKey. This is size of the keyboard at the beginning of frame change animation. You should better use UIKeyboardFrameEndUserInfoKey in order to get final frame.
if x is the height of the view with send button.
if showsKeyboard
{
cntInputViewBottom.constant = keyboardScreenBeginFrame.size.height+ x
}
else
{
cntInputViewBottom.constant = x
}
Problem is you are not informing your view/controller to update your constraint before and after.
if showsKeyboard
{
self.view.layoutIfNeeded()
cntInputViewBottom.constant = keyboardScreenBeginFrame.size.height
self.view.layoutIfNeeded()
}
else
{
self.view.layoutIfNeeded()
cntInputViewBottom.constant = 0
self.view.layoutIfNeeded()
}
just add self.view.layoutIfNeeded() before & after updating constraint as displayed.