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.
Related
I'm trying to add Swipe to right and left to collection view cell to transform the container to the right and left with a certain angle
Here is my initial setup
private func setupGestures() {
let swipeToRight = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeRight))
swipeToRight.direction = .right
container.addGestureRecognizer(swipeToRight)
let swipeToLeft = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeLeft))
swipeToLeft.direction = .left
container.addGestureRecognizer(swipeToLeft)
}
#objc func respondToSwipeRight(gesture: UIGestureRecognizer) {
let angle = CGFloat(Double.pi/2)
UIView.animate(withDuration: 0.5) {
self.container.transform = CGAffineTransform(rotationAngle: angle)
}
}
#objc func respondToSwipeLeft(gesture: UIGestureRecognizer) {
let angle = -CGFloat(Double.pi/2)
UIView.animate(withDuration: 0.5) {
self.container.transform = CGAffineTransform(rotationAngle: angle)
}
}
But it completely rotate the container, which is I don't want, I want to make it something like it, and turn back to it's initial position after a sec or two
[![how it should transform][1]][1]
And it would be so awesome that move based on Swiping position, I mean not automatically goes to that level of position, move with finger tip and when it reach there, just stop moving.
Could anyone help me to implement it, I have no idea how I can do it
Many thanks
Adding completion in UIView.animate in order to turn back to initial state. After a sec or two is depends on your duration in UIView.animate
Code will be like this
UIView.animate(withDuration: 0.5, animations: {
self.container.transform = CGAffineTransform(rotationAngle: 0.5)
}, completion: {
_ in
// turn back to the previous before transform
self.container.transform = .identity
})
And for your second question, you can implement with touchesBegan and touchesMoved or add PanGesture to handle changing position container view frame like you want.
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.
I have a custom presentation controller which shows a UIViewController as a "bottom sheet" above the current ViewController, and allows an interactive gesture to dismiss the presented modal. The modal's modalPresentationStyle is set to .current, and the transitioningDelegate is set to my custom class during init(). The current code works perfectly on iOS 11 and 12, but appears to "freeze" the device on iOS 9 and 10.
Here is where the animation takes place. I have confirmed the presentedFrame and dismissedFrame are correct.
extension BottomSheetPresentationManager: UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let key = presenting ? UITransitionContextViewControllerKey.to
: UITransitionContextViewControllerKey.from
let keyV = presenting ? UITransitionContextViewKey.to
: UITransitionContextViewKey.from
let controller = transitionContext.viewController(forKey: key)!
let view = transitionContext.view(forKey: keyV)!
if presenting {
transitionContext.containerView.addSubview(view)
}
let presentedFrame = transitionContext.finalFrame(for: controller)
var dismissedFrame = presentedFrame
switch direction {
case .left:
dismissedFrame.origin.x = -presentedFrame.width
case .right:
dismissedFrame.origin.x = transitionContext.containerView.frame.size.width
case .top:
dismissedFrame.origin.y = -presentedFrame.height
case .bottom:
dismissedFrame.origin.y = transitionContext.containerView.frame.size.height
}
let initialFrame = presenting ? dismissedFrame : presentedFrame
let finalFrame = presenting ? presentedFrame : dismissedFrame
// Runs when the animation finishes
let completionBlock: (Bool) -> Void = { (finished) in
// tell our transitionContext object that we've finished animating
if transitionContext.transitionWasCancelled {
if self.interactive {
transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(false)
} else {
if self.interactive {
finished ? transitionContext.finishInteractiveTransition() : transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(finished)
}
}
// Put what you want to animate here.
let animationBlock: () -> Void = {
view.frame = finalFrame
}
// Set up for the animation
let animationDuration = transitionDuration(using: transitionContext)
view.frame = initialFrame
// Perform a different animation based on whether we're interactive (performing a gesture) or not
if interactive {
// Do a linear animation so we match our dragging with our transition
UIView.animate(withDuration: animationDuration,
delay: 0,
options: .curveLinear,
animations: animationBlock,
completion: completionBlock)
} else {
// Do a spring animation with easing
UIView.animate(withDuration: animationDuration,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.45,
options: [.curveEaseInOut],
animations: animationBlock,
completion: completionBlock)
}
}
}
After much testing, I have figured out that the completion block on the UIView.animate block never gets called (completionBlock in the code), and in the Capture View Hierarchy view, it actually shows my presented view on top, with what looks like a screenshot of the view (UIVisualEffectView) directly under the presented menu.
What you are seeing there is my bottom menu (the UITableView with cells that you see in front), with a UIVisualEffectView behind it (appears to be a screenshot of the UICollection view behind that), and a UICollectionView that called the present() method for the menu.
What would cause the UIView.animate block not to complete? Is there any reason the behavior would differ between iOS 10 and 11? Any help would be greatly appreciated.
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
Here is my code. I am using KeyboardHelper library for managing keyboard states and getting keyboard height, and also SnapKit. I have a text field that triggers keyboard. When a keyboard is visible I want to raise blue box above keyboard bounds, so it's visible. So far I failed to do so. Can you please help me with my code?
private var keyboardHelper : KeyboardHelper?
let box = UIView()
override func viewDidLoad() {
super.viewDidLoad()
keyboardHelper = KeyboardHelper(delegate: self)
guard let superview = self.view else {
return
}
box.backgroundColor = UIColor.blue
superview.addSubview(box)
box.snp.makeConstraints { make in
make.bottom.equalTo(superview.snp.bottom).offset(-16)
make.width.equalTo(200)
make.centerX.equalTo(superview)
make.height.equalTo(100)
}
}
func keyboardWillAppear(_ info: KeyboardAppearanceInfo) {
UIView.animate(withDuration: TimeInterval(info.animationDuration), delay: 0, options: info.animationOptions, animations: {
self.box.snp.updateConstraints({ make in
make.bottom.equalTo(info.endFrame.size.height).offset(10)
})
self.box.layoutIfNeeded()
}, completion: nil)
}
According to SnapKit docs:
if you are only updating the constant value of the constraint you can use the method snp.updateConstraints
You need to use snp.remakeConstraints in your case. Furthermore, you should probably call layoutIfNeeded on superview.
Optionally you could get rid of changing constraints and just animate proper transforms on show:
box.transform = CGAffineTransform(translationX: 0.0, y: neededTranslation)
on hide:
box.transform = .identity