Handle tableView contentInset while keyboard is presented - ios

Im building a sample where i can post comments where i am using tableView to add a cell each time i post. its mostly handled but I'm suffering of one issue when i want to post a comment, the tableview bottom is the same as keyboard height but empty and if place it zero the view will move down then pushed top, and thats because I'm moving the whole view when i click on textview to write text.
This is a preview of the empty space under the tableView (keyboard height as inset) : Preview of image on Google Drive
Another image to see if i made the bottom (content inset) = 0 when keyboard is opening : Preview of second image on Google Drive
func keyboardWillShow(sender:NSNotification){
let userInfo: [NSObject : AnyObject] = sender.userInfo!
keyboardSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
var contentInsets = UIEdgeInsets()
if wasEmoj == true {
if (UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication().statusBarOrientation)) {
contentInsets = UIEdgeInsetsMake(257.0, 0.0, (self.keyboardSize.height), 0.0);
} else {
contentInsets = UIEdgeInsetsMake(257.0, 0.0, (self.keyboardSize.width), 0.0);
}
self.mytableView.contentInset = contentInsets;
self.mytableView.scrollIndicatorInsets = contentInsets
}else {
if (UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication().statusBarOrientation)) {
contentInsets = UIEdgeInsetsMake(215.0, 0.0,(self.keyboardSize.height), 0.0);
} else {
contentInsets = UIEdgeInsetsMake(215.0, 0.0, (self.keyboardSize.width), 0.0);
}
self.mytableView.contentInset = contentInsets;
self.mytableView.scrollIndicatorInsets = contentInsets
}
}
func textViewDidBeginEditing(textView: UITextView) {
self.animateTextField(commentText, up: true, movementDistance: -215, duration: 0.3)
}
///- animate the view up or down to adapt with keyboard
func animateTextField (textField:UITextView,up:Bool,movementDistance:Int,duration:NSTimeInterval){
let movement : Int = (up ? movementDistance : -movementDistance);
UIView.beginAnimations("animateTextField", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
if up == true {
UIView.setAnimationDuration(0.38)
}else {
UIView.setAnimationDuration(0.3)
}
self.view.frame = CGRectOffset(self.view.frame, 0, CGFloat(movement))
UIView.commitAnimations()
}

It's better to change the frame of the tableview in this case.
You can keep the instance of the constraint between the commentView's bottom and the bottom layout guide.
#IBOutlet weak var commentViewBottom: NSLayoutConstraint!
Then change the constant of the constraint when needed.
func keyboardWillShow(sender:NSNotification){
if let dic = sender.userInfo {
if let keyboardFrame = dic[UIKeyboardFrameEndUserInfoKey]?.CGRectValue {
if let duration = dic[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
commentViewBottom.constant = -keyboardFrame.height
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: .CurveLinear, animations: { () -> Void in
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
}
func keyboardWillHide(sender: NSNotification) {
if let dic = sender.userInfo, let duration = dic[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
commentViewBottom.constant = 0
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: .CurveLinear, animations: { () -> Void in
self.view.layoutIfNeeded()
}, completion: nil)
}
}

Related

Synchronize UIView animation with keyboard animation

I have a UIView that will change its height and position depending so it's always "stuck" to the keyboard. The problem is that there is a asynchronous animation when changing the textField, the rest works fine. Here is a video for a better understanding:
Animation
This happen even though I used UIResponder.keyboardAnimationCurveUserInfoKey for the animations.
Here are all the parts inside my code where I animate:
Notification-Handler in ViewController:
var keyboardHeight = CGFloat(0)
var duration: Any?
var curve: NSNumber?
//MARK: keyboardWillShow
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
self.keyboardHeight = keyboardRectangle.height
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]
self.duration = duration
let curve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey]
self.curve = curve as? NSNumber
if self.wishViewIsVisible {
UIView.animate(withDuration: self.duration as! TimeInterval, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(truncating: self.curve!)), animations: {
self.wishConstraint.constant = -(self.wishView.frame.height + self.keyboardHeight)
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
Closure-Handler in ViewController:
self.wishView.onPriceButtonTapped = { [unowned self] height, isHidden in
if isHidden {
UIView.animate(withDuration: self.duration as! TimeInterval, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(truncating: self.curve!)), animations: {
self.wishConstraint.constant -= height
self.wishView.priceTextField.becomeFirstResponder()
self.view.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: self.duration as! TimeInterval, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(truncating: self.curve!)), animations: {
self.wishConstraint.constant += height
self.wishView.wishNameTextField.becomeFirstResponder()
self.view.layoutIfNeeded()
}, completion: nil)
}
}
Closure inside UIView:
#objc func priceButtonTapped(){
let priceViewIsHidden = priceView.isHidden
if priceViewIsHidden {
UIView.animate(withDuration: 0.25) {
self.priceView.alpha = 1
self.priceView.isHidden = false
self.theStackView.layoutIfNeeded()
}
self.onPriceButtonTapped?(priceView.frame.height, true)
} else {
UIView.animate(withDuration: 0.25) {
self.priceView.alpha = 0
self.priceView.isHidden = true
self.theStackView.layoutIfNeeded()
}
self.onPriceButtonTapped?(priceView.frame.height, false)
}
}
Does anyone know how I can fix this issue?
I know I am hard-coding the values inside the UIView but that is just to hide/show the arrangedSubview inside the StackView and this is not where the animation is off as you can see in the video. It only looks off when changing the textFields.

Manage Keyboard in Chat View

I have a controller like chat view, with one textfield and button and table view. I want to when user tap to textfield textfield go up and scroll to end of list. there is my code :
#objc func keyboardWillChangeFrame(_ notification: Notification) {
let keyboardFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue
print("*****************")
print((keyboardFrame?.height)!)
//emoji keyboard size is 258
if (keyboardFrame?.height)! == 258 {
textBackViewBottom.constant = 52
}
else {
textBackViewBottom.constant = 10
}
}
#objc func keyboardWillShow(_ notification: Notification) {
// Check for double invocation
if _keyboardShown {
return
}
_keyboardShown = true
// Reducing size of table
let baseView = self.view
let keyboardFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey]! as AnyObject).cgRectValue
print("////////////////////")
print((keyboardFrame?.height)!)
let keyboardDuration = ((notification as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue
let visibleRows = tableView.indexPathsForVisibleRows
var lastIndexPath : IndexPath? = nil
if (visibleRows != nil) && visibleRows!.count > 0 {
lastIndexPath = visibleRows![visibleRows!.count-1] as IndexPath
}
UIView.animate(withDuration: keyboardDuration!, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: {
baseView!.frame = CGRect(x: baseView!.frame.origin.x, y: baseView!.frame.origin.y, width: baseView!.frame.size.width, height: baseView!.frame.size.height - (keyboardFrame?.size.height)!)
}, completion: {
(finished: Bool) in
if lastIndexPath != nil {
// Scroll down the table so that the last
// visible row remains visible
self.tableView.scrollToRow(at: lastIndexPath!, at: UITableViewScrollPosition.bottom, animated: true)
}
})
}
#objc func keyboardWillHide(_ notification: Notification) {
// Check for double invocation
if !_keyboardShown {
return
}
_keyboardShown = false
// Expanding size of table
//
let baseView = self.view
let keyboardFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey]! as AnyObject).cgRectValue
let keyboardDuration = ((notification as NSNotification).userInfo![UIKeyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue
UIView.animate(withDuration: keyboardDuration!, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: {
baseView!.frame = CGRect(x: baseView!.frame.origin.x, y: self.view.frame.origin.y, width: self.view.frame.size.width, height: self.view.frame.size.height + (keyboardFrame?.size.height)!)
}, completion: nil)
}
every thing work well with Iphone default keyboard, but when I'm using custom keyboard like Swiftkey my code is wrong and view broken.
How can get Update view when keyboard size and keyboard type change.
thanks for reply.

Swift UITabBarController hide with animation

I'm trying to add animation to my tabBarController when hidden. Im able to accomplish this effect with the navigationBarController by using self.navigationController?.isNavigationBarHidden = true. I'm able to hide the tabBar by using self.tabBarController?.tabBar.isHidden = true but i do not get the animation how can I do this thank you in advance.
You could change the tab bar's frame inside an animation, so something like:
func hideTabBar() {
var frame = self.tabBarController?.tabBar.frame
frame?.origin.y = self.view.frame.size.height + (frame?.size.height)!
UIView.animate(withDuration: 0.5, animations: {
self.tabBarController?.tabBar.frame = frame!
})
}
func showTabBar() {
var frame = self.tabBarController?.tabBar.frame
frame?.origin.y = self.view.frame.size.height - (frame?.size.height)!
UIView.animate(withDuration: 0.5, animations: {
self.tabBarController?.tabBar.frame = frame!
})
}
Which sets the tab bar just below the visible screen, so that it slides up/down from the bottom.
I've developed a util extension for UIViewController
Swift 4 compatible:
extension UIViewController {
func setTabBarHidden(_ hidden: Bool, animated: Bool = true, duration: TimeInterval = 0.3) {
if animated {
if let frame = self.tabBarController?.tabBar.frame {
let factor: CGFloat = hidden ? 1 : -1
let y = frame.origin.y + (frame.size.height * factor)
UIView.animate(withDuration: duration, animations: {
self.tabBarController?.tabBar.frame = CGRect(x: frame.origin.x, y: y, width: frame.width, height: frame.height)
})
return
}
}
self.tabBarController?.tabBar.isHidden = hidden
}
}
Improvement of the response of #Luca Davanzo. If the bar is already hidden, it will continue hiding it and moving it lower. Also get rid of the return, so the state of the tabbar.hidden changes when the animation happens.
So I added a check:
extension UIViewController {
func setTabBarHidden(_ hidden: Bool, animated: Bool = true, duration: TimeInterval = 0.5) {
if self.tabBarController?.tabBar.isHidden != hidden{
if animated {
//Show the tabbar before the animation in case it has to appear
if (self.tabBarController?.tabBar.isHidden)!{
self.tabBarController?.tabBar.isHidden = hidden
}
if let frame = self.tabBarController?.tabBar.frame {
let factor: CGFloat = hidden ? 1 : -1
let y = frame.origin.y + (frame.size.height * factor)
UIView.animate(withDuration: duration, animations: {
self.tabBarController?.tabBar.frame = CGRect(x: frame.origin.x, y: y, width: frame.width, height: frame.height)
}) { (bool) in
//hide the tabbar after the animation in case ti has to be hidden
if (!(self.tabBarController?.tabBar.isHidden)!){
self.tabBarController?.tabBar.isHidden = hidden
}
}
}
}
}
}
}
In case if you need to toggle it from hide to visible and vice versa:
func toggleTabbar() {
guard var frame = tabBarController?.tabBar.frame else { return }
let hidden = frame.origin.y == view.frame.size.height
frame.origin.y = hidden ? view.frame.size.height - frame.size.height : view.frame.size.height
UIView.animate(withDuration: 0.3) {
self.tabBarController?.tabBar.frame = frame
}
}
Swift 4 solution:
tabBarController?.tabBar.isHidden = true
UIView.transition(with: tabBarController!.view, duration: 0.35, options: .transitionCrossDissolve, animations: nil)
Here is a simple extension :
func setTabBar(hidden:Bool) {
guard let frame = self.tabBarController?.tabBar.frame else {return }
if hidden {
UIView.animate(withDuration: 0.3, animations: {
self.tabBarController?.tabBar.frame = CGRect(x: frame.origin.x, y: frame.origin.y + frame.height, width: frame.width, height: frame.height)
})
}else {
UIView.animate(withDuration: 0.3, animations: {
self.tabBarController?.tabBar.frame = UITabBarController().tabBar.frame
})
}
}
So I've been playing around for 3 days with this now, finding out that the one that worked for me in my code was Adriana's post from 14th Sept 2018. But I was not sure how to use the coding once copied into my Project. So, after much experimenting I found that the way I could use this func was to put the following into into the respective swipe actions.
setTabBarHidden(false)
setTabBarHidden(true)
My next step is to try to get the swipe actions working while using UIScrollView in the same UIView at the same time.
You have to add UIView transitionWithView class func
Swift 2
func hideTabBarWithAnimation() -> () {
UIView.transitionWithView(tableView, duration: 1.0, options: .TransitionCrossDissolve, animations: { () -> Void in
self.tabBarController?.tabBar.hidden = true
}, completion: nil)
}
Swift 3, 4, 5
func hideTabBarWithAnimation() -> () {
UIView.transition(with: tableView, duration: 1.0, options: .transitionCrossDissolve, animations: { () -> Void in
self.tabBarController?.tabBar.isHidden = true
}, completion: nil)
}

.xib Called Inside ViewController Does not Recognize Touch Events

Inside a UIViewController, I call a .xib file and present it over the current UIView.
// initiate the pop up ad view and display it
let popUpAdView = PopUpAdViewController(nibName: "PopUpAdView", bundle: nil)
popUpAdView.displayIntoSuperView(view)
There is a button inside that .xib file that should remove itself from the screen when it's touched. However it doesn't perform so.
What exactly am I missing?
class PopUpAdViewController: UIViewController {
#IBOutlet weak var popUpAdView: UIView!
#IBOutlet weak var closeButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// adjust the view size from the device's screen size
let screenSize = UIScreen.mainScreen().bounds
view.frame = CGRectMake(0, 64, screenSize.width, screenSize.height-64)
// cosmetically adjust the view itself
view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
view.userInteractionEnabled = true
closeButton.userInteractionEnabled = true
// style the pop up ad view
popUpAdView.layer.cornerRadius = 5
popUpAdView.layer.shadowOpacity = 0.8
popUpAdView.layer.shadowOffset = CGSizeMake(0.0, 0.0)
closeButton.addTarget(self, action: #selector(removeOutOfSuperView), forControlEvents: .TouchUpInside)
}
func displayIntoSuperView(superView: UIView!) {
superView.addSubview(self.view)
// define the initial cosmetical values of the items
popUpAdView.transform = CGAffineTransformMakeScale(0.3, 0.3)
popUpAdView.alpha = 0.0
closeButton.alpha = 0.0
// animate...
UIView.animateWithDuration(0.8, delay: 0.0, options: .CurveEaseIn, animations: {
self.popUpAdView.alpha = 1.0
self.popUpAdView.transform = CGAffineTransformMakeScale(1.0, 1.0)
}) { (Bool) in
UIView.animateWithDuration(0.6, delay: 1.5, options: .CurveEaseIn, animations: {
self.closeButton.alpha = 1.0
}, completion: { (Bool) in
})
}
}
func removeOutOfSuperView() {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseIn, animations: {
self.closeButton.alpha = 0.0
self.popUpAdView.transform = CGAffineTransformMakeScale(0.1, 0.1)
}) { (finished) in
UIView.animateWithDuration(0.8, delay: 0.0, options: .CurveEaseIn, animations: {
self.view.alpha = 0.0
self.view.removeFromSuperview()
}, completion: nil)
}
}
#IBAction func closePopUpAdView(sender: AnyObject) {
print("Closing the pop up ad...")
removeOutOfSuperView()
}
}
Update
.xib structureL:

Custom ViewController Transition

I saw a tutorial on Appcoda Transition ViewControllers transition a menu from up to bottom and I implemented it. Then, I tried to transition from bottom up using UIViewControllerContextTransitioning. But, doing it wrong cause I was setting the wrong values I think. Below is the code
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
//Get reference to our fromView, toView and the container view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
//Setup the transform for sliding
let container = transitionContext.containerView()
let height = container?.frame.height
let moveDown = CGAffineTransformMakeTranslation(0, height! - 150)
let moveUp = CGAffineTransformMakeTranslation(0, -50)
//Add both views to the container view
if isPresenting {
toView?.transform = moveUp
snapShot = fromView?.snapshotViewAfterScreenUpdates(true)
container?.addSubview(toView!)
container?.addSubview(snapShot!)
}
//Perform the animation
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.3, options: UIViewAnimationOptions(rawValue: 0), animations: {
if self.isPresenting {
self.snapShot?.transform = moveDown
toView?.transform = CGAffineTransformIdentity
} else {
self.snapShot?.transform = CGAffineTransformIdentity
fromView?.transform = moveUp
}
}, completion: {finished in
transitionContext.completeTransition(true)
if !self.isPresenting {
self.snapShot?.removeFromSuperview()
}
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return duration
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// Get reference to our fromView, toView and the container view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// Set up the transform we'll use in the animation
guard let container = transitionContext.containerView() else {
return
}
let moveUp = CGAffineTransformMakeTranslation(0, container.frame.height + 50)
let moveDown = CGAffineTransformMakeTranslation(0, -250)
// Add both views to the container view
if isPresenting {
toView.transform = moveUp
snapshot = fromView.snapshotViewAfterScreenUpdates(true)
container.addSubview(toView)
container.addSubview(snapshot!)
}
// Perform the animation
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: {
if self.isPresenting {
self.snapshot?.transform = moveDown
toView.transform = CGAffineTransformIdentity
} else {
self.snapshot?.transform = CGAffineTransformIdentity
fromView.transform = moveUp
}
}, completion: { finished in
transitionContext.completeTransition(true)
if !self.isPresenting {
self.snapshot?.removeFromSuperview()
}
})
}
This Should work. I checked out the tutorial you shared and you probably can't see the menu on the bottom because the way the MenuTableViewController.swift is set up on storyboard it is made so that the menu is always started from the top, so change that up and it should work perfectly fine. Let me know if you have any questions.

Resources