I have written a functionscrollToVisible() to scroll text in UItextview because some part of text is covered by the keyboard, or the cursor isn't in visible. But UItextview can scroll the text automatically when the cursor is not in the whole view but not visible, it can still be covered by keyboard by automatically scroll.The UItextview's auto scroll can interrupt my scrollToVisible().
Thus, can I ban the UItexview to scroll automatically? Or another way to solve "keyboard cover" problem?
My scrollToVisible() function
func scrollToVisible()
{
let cursortop = self.EditArea.convert(self.EditArea.caretRect(for: (self.EditArea.selectedTextRange?.start)!).origin, to: self.view)
var cursorbottom = cursortop
cursorbottom.y += self.EditArea.caretRect(for: (self.EditArea.selectedTextRange?.start)!).height
let bottom = UIScreen.main.bounds.size.height - self.EditArea.textContainerInset.bottom
var contentOffset = self.EditArea.contentOffset
if cursortop.y <= 85
{
contentOffset.y = contentOffset.y - 85 + cursortop.y
self.EditArea.setContentOffset(contentOffset, animated: true)
}
else if cursorbottom.y >= bottom
{
contentOffset.y = contentOffset.y - bottom + cursorbottom.y
self.EditArea.setContentOffset(contentOffset, animated: true)
}
}
PS:this EditArea is the textview
I have a similar problem: when you open the keyboard, the text view is not adjusted and the cursor hides behind the keyboard (or as you say "covers" the cursor). So if I hit enter to start a new line, it also doesn't visibly auto scroll (actually it does, but it's behind the keyboard). I found a solution, which works perfectly for me on this website: https://www.hackingwithswift.com/example-code/uikit/how-to-adjust-a-uiscrollview-to-fit-the-keyboard
Solution extracted from above website using swift 4:
Subscribe for the events when the keyboard appears and disappears in your viewDidLoad() function:
// For avoiding that the text cursor disappears behind the keyboard, adjust the text for it
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillHide, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: .UIKeyboardWillChangeFrame, object: nil)
Adjust the textview using this function, add it anywhere in your class:
// Adjusts the textView, so that the text cursor does not disappear behind the keyboard
#objc func adjustForKeyboard(notification: Notification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == Notification.Name.UIKeyboardWillHide {
textView.contentInset = UIEdgeInsets.zero
} else {
textView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
textView.scrollIndicatorInsets = textView.contentInset
let selectedRange = textView.selectedRange
textView.scrollRangeToVisible(selectedRange)
}
Here is my solution, where you need not worry to handle any textfield/textview in the app by writing just one line of code in app delegate
If you are using pods, the you can add "IQKeyboardManager" pods by just adding the following pods
pod 'IQKeyboardManagerSwift'
and add this line in didFinishLaunchingWithOptions in app delegate
IQKeyboardManager.sharedManager().enable = true
Related
I am trying to dismiss the keyboard on swipe down but my input accessory view (custom uiview class) is not sticking to it on swipe down. It leaves a space between it and the keyboard and is not syncing up with it and goes down only after the keyboard disappears. It works fine when activating the keyboard by tapping on the text view/cmd+k to toggle it up and down, but not when swiping it down. From the image I am swiping down to dismiss the keyboard but there is a giant gap. [1]: https://i.stack.imgur.com/xLV6u.jpg (Sorry if my formatting is bad, I'm still getting used to actually posting on stackoverflow)
Here is my code so far pertaining to it:
// accessory view is anchor to the bottom to start
func layoutInputAccessoryView() {
view.addSubview(inputFieldAccessoryView)
inputFieldAccessoryView.anchor(right: view.rightAnchor, left: view.leftAnchor)
inputFieldAccessoryViewBottomAnchor = inputFieldAccessoryView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
inputFieldAccessoryViewBottomAnchor?.isActive = true
}
// swiping to dismiss in collection view
collectionView.keyboardDismissMode = .interactive
// listener for when the keyboard shows/hides
func addKBObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(handleKBWilShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKBWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
// method for when the keyboard shows
#objc func handleKBWilShow(notification: Notification) {
if let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let rect = frame.cgRectValue
let height = rect.height
inputFieldContainerBottom?.constant = -height
view.layoutIfNeeded()
}
}
// selector method called when keyboard will hide
#objc func handleKBWillHide(notification: Notification) {
if let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double {
UIView.animate(withDuration: duration) {
self.inputFieldContainerBottomAnchor?.constant = 0
self.view.layoutIfNeeded()
}
}
}
I have designed screen using storyboard constraints for iPhone XR. if i run that on iPhone7 then it's keyboard hides textfields.. so i have return code for textfield up while keyboard appears.. but here textfield going up for every iPhone sizes.. here i want to move textfield up while keyboard enter according to screen size. how?
here is my code:
func animateViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:TimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration )
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.self.conformPasswordTextField {
animateViewMoving(up: true, moveValue: 60)
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == self.self.conformPasswordTextField {
animateViewMoving(up: false, moveValue: 60)
}
}
How to move up textfield according to iPhone sizes.
Please help me in the code.
The best way to design and develop applications is to ALWAYS develop an application on the SMALLEST screen size. Scaling up applications is always easier then it is to scale an app down.
Based on your question, what you are asking to do is (other ways of doing this) creating variables that incorporate all the screen sizes width and height, and setting each object width height and so on according to the screen size.
This is not best practice.
The best way to do this is to use 'vary for traits' in XCode Story board.
What is 'Vary for Traits' in Xcode 8?
Referencing this question above is a good place to start.
However my recommendation is to redesign your application to fit on the SMALLEST ios screen size you are willing to support.
Like I said it is always much easier to scale up then to scale down and up
If you don't want to use auto layout and wants textfield to go up when keyboard appears then use UIScrollView.
and use following code. make sure you change it according to your text fields
call this function registerforkeyboardnotifiations from your viewdidload
lazy var securityScrollView = UIScrollView()
var activeTextField = UITextField()
var testTextField = UITextField()
//call setup screen from viewdidload
func setUpScreen {
view.addSubview(securityScrollView)
// adding textfield to scroll view
securityScrollView.addSubview(testTextField)
// make sure to setup constraints. for textfield and scrollview
}
/// here I am setting my activekeyboard
public func textFieldDidBeginEditing(_ textField: UITextField) {
activeTextField = textField
}
func registerForKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardAppear(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardDisappear(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
}
#objc func onKeyboardAppear(_ notification: NSNotification) {
let info = notification.userInfo!
let rect: CGRect = info[UIResponder.keyboardFrameBeginUserInfoKey] as! CGRect
let kbSize = rect.size
let insets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height+120, right: 0) // bottom constants change according to your needs
self.securityScrollView.contentInset = insets
self.securityScrollView.scrollIndicatorInsets = insets
var visibleRect: CGRect = self.securityScrollView.convert(self.securityScrollView.bounds, to: self.view)
visibleRect.size.height -= rect.size.height;
let inputRect: CGRect = self.activeTextField.convert(self.activeTextField.bounds, to: self.securityScrollView)
if (visibleRect.contains(inputRect)) {
self.securityScrollView.scrollRectToVisible(inputRect, animated: true)
}
}
#objc func onKeyboardDisappear(_ notification: NSNotification) {
self.securityScrollView.contentInset = UIEdgeInsets.zero
self.securityScrollView.scrollIndicatorInsets = UIEdgeInsets.zero
}
make sure you are removing observer and best place to do it is in deist if you are using view controller
deinit {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
}
If you want to setup your scrollview refer this
public override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
securityScrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + kPaddig)
securityScrollView.isScrollEnabled = true
securityScrollView.showsVerticalScrollIndicator = false
}
change active textfield for your text field
var activeTextField = UITextField()
Make changes according to your scrollview and textbook. Enjoy!
I have an textview at bottom of screen and search bar at top of screen. Following is my code to solve the problem of keyboard when textview is pressed
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(UIView.keyboardWillChange(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
},completion: {(true) in
self.layoutIfNeeded()
})
}
}
But when I press search bar then the screen moves up and search bar disappears. If I do view.bindToKeyboard() then the edittext is proper after displaying the keyboard.
One solution which I tried was binding the outlet of textview to keyboard but the textview disappears as soon as I start typing.
I think the problem is that you are trying to know when the keyboard is going to appear. Searchbar has a textfield. So when it's tapped it opens the keyboard like your textview and the keyboardWillChange is called.
keyboardWillChange(_ notification: NSNotification)
So the keyboard appears and hides your searchbar. You can detect searchbar tap and cancel the notification there.
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool
{
//Dismiss your keyboard notification here
return true
}
You should use a library to handle this situation. I'm suggesting IQKeyboardManager. IQKeyBoardManager Github.
I edit my earlier respond... have you try to put your searchBar into navigationBar?
something like this (just put on viewDidLoad):
navigationItem.titleView = searchBar
I want to show a custom UIView above my keyboard, when my text field is becoming first responder.
However, it seems that I can show view and keyboard both at the same time?
Is there is a way to overcome it?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
phoneInputTextField.becomeFirstResponder()
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillChangeFrame , object: nil)
}
func handleKeyboardNotification(notification: NSNotification) {
guard let userInfo = notification.userInfo,
let frameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
else { return }
let customView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: frameValue.cgRectValue.width + 20))
customView.backgroundColor = UIColor.black
phoneInputTextField.inputView = customView
}
That code shows only keyboard.
You can use inputView and inputAccessoryView both.
inputView is used to assign a some custom view in replacement of UIKeyboard , like you can use UIPickerView , UIDatePicker etc while editing the textField
inputAccessoryView is also used to assign a some custom view but without replacing a UIKeyboard , it renders above the keyboard . like you can use UIToolbar above the keyboard and many other View as per your requirements.
In your case you can use inputAccessoryView
example :
yourTxtField.inputAccessoryView = yourCustomView()
I have looked around and found this post about moving a view when a keyboard appears. It works great and moves the keyboard anytime I click in a UITextField. The issue is that I have three UITextFields, and the keyboard should only move when it is going to present over a UITextField. I looked at the Apple documentation on this as well, and found some useful information but I am still not getting the desired functionality.
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
var aRect = self.view.frame;
aRect.size.height -= keyboardSize.size.height
if self.view.frame.origin.y == 0{
if aRect.contains(activeField.frame.origin){
self.view.frame.origin.y -= keyboardSize.height
}
}
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
activeField = textField
}
func textFieldDidEndEditing(_ textField: UITextField) {
activeField = nil
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.view.frame.origin.y += keyboardSize.height
}
}
}
From the Apple documentation I just took the piece where I create the aRect, and then check if the points intersect with the contains function. I would expect this to then make the view move only when the keyboard were to overlap with a textfield, and keep the view in place otherwise. For some reason that I don't fully understand, this is not the case. The keyboard will move the view in the case where any textfield is clicked (even though for some it shouldn't). I have played around with it a bit now and tried debugging but have been unsuccessful. Any ideas?
EDIT: I did a little debugging and it seems that the aRect.contains(...) is returning true for when all textfields are clicked, but in reality it should not. Is contains the right method to be using?
I followed this way to manage such issue in TableView same way you can manage in your view Here is step by step code:
within viewDidLoad added registerForKeyboardNotifications()
Here is the method
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
Again define other method :
func keyboardWasShown(aNotification: NSNotification) {
let info = aNotification.userInfo as! [String: AnyObject],
kbSize = (info[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue().size,
contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
electricalViewListTableview.contentInset = contentInsets
electricalViewListTableview.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
var aRect = self.view.frame
aRect.size.height -= kbSize.height
if let activeTF = activeField {
if !CGRectContainsPoint(aRect, activeTF.frame.origin) {
electricalViewListTableview.scrollRectToVisible(activeTF.frame, animated: true)
}
}
}
Keyboard Hiding Method :
func keyboardWillBeHidden(aNotification: NSNotification) {
let contentInsets = UIEdgeInsetsZero
electricalViewListTableview.contentInset = contentInsets
electricalViewListTableview.scrollIndicatorInsets = contentInsets
}
After this use UITextFieldDelegates method to keep track active textfield :
var activeField: UITextField?
func textFieldDidBeginEditing(textField: UITextField) {
self.activeField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
self.activeField = textField
}
Hope it helps!
You have two main issues with your keyboardWillShow code.
You are using the wrong key to get the keyboard frame. You need UIKeyboardFrameEndUserInfoKey, not UIKeyboardFrameBeginUserInfoKey. You want to know where the keyboard will end up, not where it starts from.
Once you get the keyboard's frame, you need to convert it to local coordinates. It is given to you in screen coordinates.
Your updated code would be:
func keyboardWillShow(notification: NSNotification) {
if let keyboardScreenFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardLocalFrame = self.view.convert(keyboardScreenFrame, from: nil)
var aRect = self.view.frame;
aRect.size.height -= keyboardLocalFrame.size.height
if self.view.frame.origin.y == 0 {
if aRect.contains(activeField.frame.origin) {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
}
You also have a big problem with your keyboardWillHide method. Since you keyboardWillShow method always shortens your view's frame, your keyboardWillHide method also needs to always restore the view's frame height.
If I were you, I wouldn't change the view's frame height in either method. Just adjust its origin as needed to make the text field visible.
Try IQKeyboardManager . It automatically manages text fields to make them visible. You just need to add it to your project, and no need to write even one line of code. A piece from it's documentation:
Often while developing an app, We ran into an issues where the iPhone
keyboard slide up and cover the UITextField/UITextView.
IQKeyboardManager allows you to prevent issues of the keyboard sliding
up and cover UITextField/UITextView without needing you to enter any
code and no additional setup required. To use IQKeyboardManager you
simply need to add source files to your project.
EDIT: In addition to Rmaddy's answer, I can say you should consider changing if aRect.contains(activeField.frame.origin) to if !aRect.contains(activeField.frame), because the first check will return true even if the top of your textfield is in the frame of the view, and also, you should be checking if it doesn't contain the frame of your textfield, then change the frame of the view.
And, I'm not totally sure, but, maybe it would be better if you move your activeField = textField code to the textFieldShouldBeginEditing delegate method.