I want to add an option when clicking on a cell it will show floating text box above the keyboard and its blurs the background.
Anyone familiar with this and how to implement it?
You can view image at the following link:
Start with adding some properties to your class:
var textField = UITextField()
var composeBarView = UIView()
var blurView = UIView()
let barHeight:CGFloat = 44
Next create the blur view, textField and container view. Do this in the viewWillAppear:
blurView = UIView(frame: self.view.frame)
blurView.alpha = 0.5
blurView.backgroundColor = UIColor.blackColor();
self.view.addSubview(blurView);
blurView.hidden = true;// Note its hidden!
textField = UITextField(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, barHeight))
composeBarView = UIView(frame: CGRectMake(0, UIScreen.mainScreen().bounds.height-64, UIScreen.mainScreen().bounds.width, barHeight));
composeBarView.addSubview(textField);
self.view.addSubView(composeBarView);
Then you should register for the UIKeyboardWillShowNotification and UIKeyboardWillHideNotification notifications:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillToggle:", name: UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillToggle:", name: UIKeyboardWillHideNotification, object: nil);
Implement the keyboardWillToggle method:
func keyboardWillToggle(notfication: NSNotification){
if let userInfo = notfication.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let startFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue();
let duration:NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
var signCorrection:CGFloat = 1;
if (startFrame.origin.y < 0 || startFrame.origin.x < 0 || endFrame.origin.y < 0 || endFrame.origin.x < 0){
signCorrection = -1;
}
let widthChange = (endFrame.origin.x - startFrame.origin.x) * signCorrection;
let heightChange = (endFrame.origin.y - startFrame.origin.y) * signCorrection;
let sizeChange = UIInterfaceOrientationIsLandscape(self.interfaceOrientation) ? widthChange : heightChange;
var frame = composeBarView.frame
frame.origin.y += (sizeChange - barHeight ?? 0.0 )
composeBarView.frame = frame;
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
Note that we must account for the bar height, when you show the keyboard. You will need to adjust the view back to its original position.
Then when the didSelectRowAtIndexPath is called you call:
textFiled.becomeFirstResponder()
blureView.hidden = false;
Not tested yet but i think that you can do it with a modal segue?
For an exemple, when your textfield become editing you can segue to a new CellViewcontroller which can appear above your actual Tableview and insert this parameters :
func prensentationController(controller: UIPresentationController, viewControllerdaptivePresentationStyle style: UIModalPresentationtyle) -> UIViewController
let navcon = UINavigationController(UIPresentationViewController: controller.presentedViewController)
let visualEffectView = UIVisualEffectView(effect:UIBlurEffect(style: .ExtraLight))
visualEffectView.frame = navcon.view.bounds
navcon.view.insertSubview(visualEffectView, atIndex: 0)
return navcon
}
Related
I have a UITableView with multiple textfields. When I click on a textfield, I setContentOffset to bring the textfield to the top of the page, but I do not have enough inset to do so when the keyboard appears. So I added inset to the bottom of the view. This allows me to scroll all the way down but now when I select a field it scrolls down too far, further than the setContentOffset that I set. I've tried different content inset values, but none work. Is there a way to fix this content inset issue?
func adjustInsetForKeyboardShow(_ show: Bool, notification: Notification) {
if !keyboardAdjusted || !show {
guard let value = (notification as NSNotification).userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardFrame = value.cgRectValue
var adjustmentHeight = (keyboardFrame.height + 100) * (show ? 1 : -1)
adjustmentHeight = (adjustmentHeight < 0.0) ? 0.0 : adjustmentHeight
self.editPaymentTableView.contentInset.bottom = adjustmentHeight
self.editPaymentTableView.scrollIndicatorInsets.bottom = adjustmentHeight
}
keyboardAdjusted = show
}
I've found this on SO some time ago, but can't find the link to it anymore, here is the code that very neatly handles the keyboard and the insets of the tableview. Nice thing is it's for showing and hiding. No need to implement other keyboard notifications:
public func keyboardWillChangeFrame(notification: NSNotification) {
guard
let userInfo = notification.userInfo,
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let duration: TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue,
let animationCurveRawNSNumber = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
else {
return
}
let animationCurveRaw = animationCurveRawNSNumber.uintValue
let animationCurve: UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
let offsetY: CGFloat
if endFrame.origin.y >= UIScreen.main.bounds.size.height {
offsetY = 0.0
} else {
offsetY = endFrame.size.height
}
let contentInsets = UIEdgeInsetsMake(0, 0, offsetY, 0)
UIView.animate(withDuration: duration, delay: TimeInterval(0), options: animationCurve, animations: {
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
}, completion: nil)
}
In terms of your solution, you can replace these two lines:
var adjustmentHeight = (keyboardFrame.height + 100) * (show ? 1 : -1)
adjustmentHeight = (adjustmentHeight < 0.0) ? 0.0 : adjustmentHeight
with:
var adjustmentHeight = keyboardFrame.height * (show ? 1 : 0)
And to be sure that you're also setting top inset to 0 do:
let contentInsets = UIEdgeInsetsMake(0, 0, adjustmentHeight, 0)
self.editPaymentTableView.contentInset = contentInsets
I am working on a chat app (think whatsapp). In one viewcontroller a user will pick a photo to send and then input their message in a textfield. The first time the textfield input moves with the keyboard height. That works fine. Now if a use chose the wrong image and clicked back and selected another image from the gallery or a photo the textfield does not move with the keyboard.
My code is:
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
func keyboardWillShow(_ note: Notification) {
if let keyboardSize = note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
let keyboardHeight = keyboardSize.height
let duration = (note.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Float)
let curve = (note.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! CInt)
//----------------Image Attachment----------------
let imageX = self.imgBackgroundSend.frame.origin.x
let imageY = CGFloat(keyboardHeight)
let imageWidth = self.imgBackgroundSend.frame.size.width
let imageHeight = self.imgBackgroundSend.frame.size.height
self.imgBackgroundSend.frame = CGRect(x: imageX, y: imageY, width: imageWidth, height: imageHeight)
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(CDouble(duration))
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: Int(CInt(curve)))!)
UIView.commitAnimations()
let offsetScrollPoint = CGPoint(x: CGFloat(0), y: CGFloat(keyboardHeight))
self.scrlViewImageText.contentOffset = offsetScrollPoint
}
}
func keyboardWillHide(_ note: Notification) {
if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardSize.height
let duration = (note.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double)
let curve = (note.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! CInt)
//----------------Image Attachment----------------
let imageX = self.imgBackgroundSend.frame.origin.x
let imageY = self.scrlViewImageText.center.y - (self.imgBackgroundSend.frame.size.height/2)
print(keyboardHeight)
let imageWidth = self.imgBackgroundSend.frame.size.width
let imageHeight = self.imgBackgroundSend.frame.size.height
self.imgBackgroundSend.frame = CGRect(x: imageX, y: imageY, width: imageWidth, height: imageHeight)
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(CDouble(duration))
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: Int(CInt(curve)))!)
UIView.commitAnimations()
let offsetScrollPoint = CGPoint(x: CGFloat(0), y: CGFloat(0))
self.scrlViewImageText.contentOffset = offsetScrollPoint
}
`
I am only a junior dev and am struggling to figure this one out as easy as it probably is
Aditya Srivastava was correct - changing the UIKeyboardFrameBeginUserInfoKey with UIKeyboardFrameEndUserInfoKey worked a treat.
Thank you so much.
As an aside - I also wanted to make the UITextView expand upwards not downwards so it does go below the keyboard. I did using this code snippet.
var frame: CGRect = txtMessage.frame
frame.origin.y = frame.maxY - txtMessage.contentSize.height
frame.size.height = txtMessage.contentSize.height
txtMessage.frame = frame
The new Safe Area layout guide introduced in iOS 11 works great to prevent content from displaying below bars, but it excludes the keyboard. That means that when a keyboard is displayed, content is still hidden behind it and this is the problem I am trying to solve.
My approach is based on listening to keyboard notifications and then adjusting the safe area through additionalSafeAreaInsets.
Here is my code:
override func viewDidLoad() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
//MARK: - Keyboard
extension MyViewController {
#objc func keyboardWillShow(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded();
}
}
#objc func keyboardWillHide(notification: NSNotification) {
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded();
}
}
#objc func keyboardWillChange(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded();
}
}
}
This works well as the MyController is a UIViewController with a UITableView that extends through the whole safe area. Now when the keyboard appears, the bottom is pushed up so that no cells are behind the keyboard.
The problem is with bottom bars. I also have a toolbar at the bottom which is already included in the safe area. Therefore, setting full keyboard height as additional safe area inset pushes the bottom of the table view up too much by exactly the height of the bottom bar. For this method to work well, I must set the additionalSafeAreaInsets.bottom to be equal to the keyboard height minus the height of the bottom bar.
Question 1: What is the best way to get the current safe area gap on the bottom? Manually get frame of toolbar and use its height? Or is it possible to get the gap directly from the safe area layout guide?
Question 2: Presumably it should be possible for the bottom bar to change size without the keyboard changing size so I should also implement some method listening to change in frame of the bar. Is this best done in viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)? Or elsewhere?
Thank you
What seems to be working for me is to calculate the intersection between view.safeAreaLayoutGuide.layoutFrame and the keyboard frame, and then setting the height of that as the additionalSafeAreaInsets.bottom, instead of the whole keyboard frame height. I don't have a toolbar in my view controller, but I do have a tab bar and it is accounted for correctly.
Complete code:
import UIKit
public extension UIViewController
{
func startAvoidingKeyboard()
{
NotificationCenter.default
.addObserver(self,
selector: #selector(onKeyboardFrameWillChangeNotificationReceived(_:)),
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
func stopAvoidingKeyboard()
{
NotificationCenter.default
.removeObserver(self,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
#objc
private func onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification)
{
guard
let userInfo = notification.userInfo,
let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
else {
return
}
let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
let intersection = safeAreaFrame.intersection(keyboardFrameInView)
let keyboardAnimationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey]
let animationDuration: TimeInterval = (keyboardAnimationDuration as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)
UIView.animate(withDuration: animationDuration,
delay: 0,
options: animationCurve,
animations: {
self.additionalSafeAreaInsets.bottom = intersection.height
self.view.layoutIfNeeded()
}, completion: nil)
}
}
If you need support back to pre IOS11 versions you can
use the function from Fabio and add:
if #available(iOS 11.0, *) { }
final solution:
extension UIViewController {
func startAvoidingKeyboard() {
NotificationCenter.default.addObserver(self,
selector: #selector(_onKeyboardFrameWillChangeNotificationReceived(_:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
}
func stopAvoidingKeyboard() {
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
}
#objc private func _onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) {
if #available(iOS 11.0, *) {
guard let userInfo = notification.userInfo,
let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
let intersection = safeAreaFrame.intersection(keyboardFrameInView)
let animationDuration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
let animationCurve = UIViewAnimationOptions(rawValue: animationCurveRaw)
UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: {
self.additionalSafeAreaInsets.bottom = intersection.height
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
I use a different approach. I have a view (KeyboardProxyView) that I add to my view hierarchy.
I pin this to the bottom of the main view, and adjust its height with the keyboard. This means we can treat the keyboardProxy as if it is the keyboard view - except that it is a normal view, so you can use constraints on it.
This allows me to constrain my other views relative to the keyboardProxy manually.
e.g. - my toolbar isn't constrained at all, but I might have inputField.bottom >= keyboardProxy.top
Code below
(note - I use HSObserver and PureLayout for notifications and autolayout - but you could easily rewrite that code if you prefer to avoid them)
import Foundation
import UIKit
import PureLayout
import HSObserver
/// Keyboard Proxy view will mimic the height of the keyboard
/// You can then constrain other views to move up when the KeyboardProxy expands using AutoLayout
class KeyboardProxyView: UIView, HSHasObservers {
weak var keyboardProxyHeight: NSLayoutConstraint!
override func didMoveToSuperview() {
keyboardProxyHeight = self.autoSetDimension(.height, toSize: 0)
let names = [UIResponder.keyboardWillShowNotification,UIResponder.keyboardWillHideNotification]
HSObserver.init(forNames: names) { [weak self](notif) in
self?.updateKeyboardProxy(notification: notif)
}.add(to: self)
activateObservers()
}
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
func updateKeyboardProxy(notification:Notification){
let userInfo = notification.userInfo!
let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let convertedKeyboardEndFrame = self.superview!.convert(keyboardEndFrame, from: self.window)
let rawAnimationCurve = (notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uint32Value << 16
let animationCurve = UIView.AnimationOptions(rawValue:UInt(rawAnimationCurve))
keyboardProxyHeight.constant = self.superview!.bounds.maxY - convertedKeyboardEndFrame.minY
//keyboardProxyHeight.constant = keyboardEndFrame.height
UIView.animate(withDuration: animationDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
self.parentViewController?.view.layoutIfNeeded()
}, completion: nil)
}
}
bottom inset:
var safeAreaBottomInset: CGFloat = 0
if #available(iOS 11.0, *) {
safeAreaBottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
}
whole keyboard function:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.onKeyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
#objc private func onKeyboardWillChangeFrame(_ notification: NSNotification) {
guard let window = self.view.window,
let info = notification.userInfo,
let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval,
let animationCurve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
else {
return
}
var safeAreaInset: CGFloat = 0
if #available(iOS 11.0, *) {
safeAreaInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
}
self.keyboardConstraint.constant = max(0, self.view.frame.height - window.convert(keyboardFrame, to: self.view).minY - safeAreaInset)
UIView.animate(
withDuration: duration,
delay: 0,
options: [UIView.AnimationOptions(rawValue: animationCurve.uintValue), .beginFromCurrentState],
animations: { self.view.layoutIfNeeded() },
completion: nil
)
}
Excluding the bottom safe area worked for me:
NSValue keyboardBounds = (NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.FrameEndUserInfoKey);
_bottomViewBottomConstraint.Constant = keyboardBounds.RectangleFValue.Height - UIApplication.SharedApplication.KeyWindow.SafeAreaInsets.Bottom;
View.LayoutIfNeeded();
I have a UIScrollView and content inside it. Inside content there is a UITextView. When user presses the UITextView I want the UITextView stick to the keyboard.
In the image:
Whole thing is UIScrollView
Bottom black area is the visible screen
Red area is content
Blue area is UITextView
Green distance is the dynamic margin between content and the screen bound.
I want to calculate the green distance which the user can see along with the blue are(UITextView) which the user can see. If the user half swiped UITextView, the UITextView should still stick to the keyboard.
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let keyboardBeginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as UInt
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as Double
let options = UIViewAnimationOptions(curve << 16)
UIView.animateWithDuration(duration, delay: 0, options: options,
animations: {
var visibleGreen = ???
var visibleBlue = ???
var amountToSubtract = visibleGreen + visibleBlue
var newFrame = (self.currentCardInstance?.newCommentCell.frame)!
var kbFrameEnd = self.view.convertRect(keyboardEndFrame, toView: nil)
var kbFrameBegin = self.view.convertRect(keyboardBeginFrame, toView: nil)
newFrame.origin.y -= kbFrameBegin.origin.y - kbFrameEnd.origin.y + amountToSubtract
self.currentCardInstance?.newCommentCell.frame = newFrame;
},
completion: nil
)
Here is how I solved it:
var keyboardModifier: CGFloat = 0
func keyboardWillAppear(notification: NSNotification) {
println("keyboardWillAppear")
keyboardResize(notification: notification)
scrollToBottom()
}
func keyboardWillDisappear(notification: NSNotification) {
println("keyboardWillDisappear")
keyboardResize(notification: notification)
}
func keyboardResize(#notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let keyboardBeginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as UInt
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as Double
let options = UIViewAnimationOptions(curve << 16)
var newFrame = (self. currentCardInstance?.frame)!
var kbFrameEnd = self.view.convertRect(keyboardEndFrame, toView: nil)
var kbFrameBegin = self.view.convertRect(keyboardBeginFrame, toView: nil)
keyboardModifier = kbFrameBegin.origin.y - kbFrameEnd.origin.y
scrollView.frame.size.height -= keyboardModifier
}
func scrollToBottom() {
var bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: false)
}
I have set my UIkeyboarddismissmode to be .Interactive, and I do not know how to resize the UITextView so that the keyboard would not cover the content. I also have a UIToolbar as the inputaccessory.
Here is my code.
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.addDoneButtonOnKeyboard()
self.textView.keyboardDismissMode = .Interactive
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
// Add notification about keyboard
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardNotification:"), name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardNotification:"), name:UIKeyboardWillHideNotification, object: nil)
// NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardFrameDidChange:"), name:UIKeyboardWillChangeFrameNotification, object: nil)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(true)
NSNotificationCenter.defaultCenter().removeObserver(UIKeyboardWillShowNotification)
NSNotificationCenter.defaultCenter().removeObserver(UIKeyboardWillHideNotification)
}
// The UIToolbar
var toolbar = UIToolbar()
func addDoneButtonOnKeyboard()
{
var doneToolbar: UIToolbar = UIToolbar(frame: CGRectMake(0, 0, 320, 44))
doneToolbar.barStyle = UIBarStyle.Default
var flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
var photo: UIBarButtonItem = UIBarButtonItem(title: "Add Photo", style: UIBarButtonItemStyle.Bordered, target: self, action: Selector("photoButtonAction"))
var done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Done, target: self, action: Selector("doneButtonAction"))
var items = NSMutableArray()
items.addObject(photo)
items.addObject(flexSpace)
items.addObject(done)
doneToolbar.items = items
doneToolbar.sizeToFit()
doneToolbar.tintColor = UIColor(red: 240/225, green: 42/225, blue: 20/225, alpha: 1)
doneToolbar.translucent = true
self.toolbar = doneToolbar
self.textView.inputAccessoryView = self.toolbar
}
func keyboardFrameDidChange(notification: NSNotification) {
if let userInfo = notification.userInfo {
var endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
var beginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue()
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
var animationDuration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
var newFrame = self.textView.frame
var keyboardFrameEnd = self.view.convertRect(endFrame!, toView: nil)
var keyboardFrameBegin = self.view.convertRect(beginFrame!, toView: nil)
newFrame.origin.y -= (keyboardFrameBegin.origin.y - keyboardFrameEnd.origin.y)
self.textView.frame = newFrame
UIView.animateWithDuration(animationDuration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
The above code only makes the textView move in a weird way and I don't know how to achieve the effect like the Message app.
Update:
func keyboardFrameEnd(notification: NSNotification) {
if let userInfo = notification.userInfo {
var endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
// Change contentInset
self.textView.contentInset.bottom = endFrame!.height
self.textView.scrollIndicatorInsets.bottom = endFrame!.height
}
}
I changed my code to above. But sometimes when I release the keyboard halfway, the scrollView would suddenly bounce to the bottom which is very weird. Any suggestion?
UITextView is a scroll view. You do not need to resize it when the keyboard appears: you should adjust it's contentInset property, increasing the .bottom by the height of the keyboard.
It can apply whether you have tabbar or not.
And it would perfectly restore view that moved by keyboard to original position.
// Set this up in storyboard first.
// It's a vertical spacing constraint between your text view and bottom of superview.
#IBOutlet weak var bottomSpacingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Receive(Get) Notification
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardNotification:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardNotification:", name: UIKeyboardWillHideNotification, object: nil)
self.originalConstraint = self.keyboardHeightLayoutConstraint?.constant //for original coordinate.
}
func keyboardNotification(notification: NSNotification) {
let isShowing = notification.name == UIKeyboardWillShowNotification
var tabbarHeight: CGFloat = 0
if self.tabBarController? != nil {
tabbarHeight = self.tabBarController!.tabBar.frame.height
}
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
let duration:NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
self.keyboardHeightLayoutConstraint?.constant = isShowing ? (endFrame!.size.height - tabbarHeight) : self.originalConstraint!
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
Move textfield when keyboard appears swift