Get sender on NotificationCenter keyboard will show - ios

I am currently working on an app with multiple text fields and 2 text views in one scroll view. On appearance of keyboard, I have been able to change the content inset of the scroll view in order to allow the fields to not be hidden. The problem I am having is I can only get this to work on one hardcoded field. I have to choose one and animate to it. Is there anyway to get the sender field or text view on keyboardWillShow? Currently, I am using this and everything works except as mentioned I had to choose one field detailsTxtView and animate to it. Any help?
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)),name: NSNotification.Name.UIKeyboardWillShow, object: nil)
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let contentInsets: UIEdgeInsets? = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0)
scrollView.contentInset = contentInsets!
scrollView.scrollIndicatorInsets = contentInsets!
let goto = CGPoint(x: CGFloat(0.0), y: CGFloat(detailsTxtView.frame.origin.y + (keyboardSize.height + 40)))
scrollView.setContentOffset(goto, animated: true)
}
}

In iOS you can figure out who has the keyboard focus with UIResponder.isFirstResponder (note all UIViews inherit from UIResponder, so its a property on every view). Just check which field has isFirstResponder = true and scroll to that one. Its useful to put all of your fields into an outlet collection if you have a lot of them so you can just iterate through them and figure out which one isFirstResponder.

Related

Customize automatic UIScrollView scrolling with UITextField

I would like to customize the scroll-offset when showing the keyboard. As you can see in the GIF, the Textfields are quite close to the keyboard and I would like to have a custom position. The "Name" textfield should have 50px more distance and the "Loan Title" textfield should just scroll to the bottom of my UIScrollView.
To be able to scroll past the keyboard I'm changing the UIScrollView insets. Strangely iOS automatically scrolls to the firstResponder textfield (see GIF).
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
// get the Keyboard size
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// update edge insets for scrollview
self.mainScrollView.scrollIndicatorInsets.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
self.mainScrollView.contentInset.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
}
I already tried to use the UITextfieldDelegate method: textFieldDidBeginEditing(_ textField: UITextField)
I also tried to use the Apple way described here: https://stackoverflow.com/a/28813720/7421005
None of these ways let me customize the automatic scroll position. In fact it kind of overrides every attempt. Does anyone know a way to workaround this?
You can prevent your view controller from automatically scrolling by setting automaticallyAdjustsScrollviewInsets to false as described here.
Implementing keyboard avoidance is also pretty straight forward. You can see how to do it here.
I don't believe there is any way to keep the automatic positioning and apply your own custom offset. You could experiment with making text field contained in another larger view and making that larger view the first responder, but that would be a hack at best.
I found a solution by myself. The problem was that the automatic scroll (animation) was interfering with my scrollRectToVisible call. Putting this in async fixed the problem.
It now looks similar to this:
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
// get the Keyboard size
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// update edge insets for scrollview
self.mainScrollView.scrollIndicatorInsets.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
self.mainScrollView.contentInset.bottom = keyboardEndFrame.height - self.view.layoutMargins.bottom
var frame = CGRect.zero
if nameTextField.isFirstResponder {
frame = CGRect(x: nameTextField.frame.origin.x, y: nameTextField.frame.origin.y + 50, width: nameTextField.frame.size.width, height: nameTextField.frame.size.height)
}
if titleTextField.isFirstResponder {
frame = CGRect(x: titleTextField.frame.origin.x, y: titleTextField.frame.origin.y + titleShortcutsCollectionView.frame.height + 25, width: titleTextField.frame.size.width, height: titleTextField.frame.size.height)
}
DispatchQueue.main.async {
self.mainScrollView.scrollRectToVisible(frame, animated: true)
}
}

How can I keep UITableView in view when keyboard appears?

I'm trying to create a page in an app that's your standard style messaging screen. I'm having trouble getting everything to position correctly when the keyboard slides into view. I'll post screenshots (sadly not inline), but here is my structure:
VIEWCONTROLLER
|-View
|-Scroll View
|-Content View
|-TextField
|-TableView (messages)
Everything is showing up as I would like it to when first loaded: If there aren't enough messages to fill the screen, the messages start at the top followed by a gap, and the text field is pinned to the bottom. Nothing scrolls. If there are a lot of messages, I am successfully scrolling the table to the last row and the textfield is pinned to the bottom of the screen still.
When the textfield is activated however, and there aren't a lot of messages, the gap between the table and the textfield remains and the messages are pushed out of view to the top.
I am trying to get the gap to shrink so the messages stay. This is standard in other messaging apps, but I cannot figure out how to do it
Initial view
Textfield activated, keyboard appears
Scrolling to display messages hides the textfield
UI Layout and constraints
Lastly, here is the code I have for keyboardWillShow. You'll notice some comments of things I have tried unsuccessfully.
func keyboardWillShow(notification:NSNotification) {
var userInfo = notification.userInfo!
let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardFrame!.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
// scrollViewBottomConstraint.constant = keyboardFrame!.height - bottomLayoutGuide.length
// contentViewHeightConstraint.constant = -keyboardFrame!.height
// self.notificationReplyTable.frame.size.height -= keyboardFrame!.height
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardFrame!.height
if let activeField = self.activeField {
if(!aRect.contains(activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
I feel like the piece I'm missing is pretty small, but just don't know enough Swift 3 to nail this. Thank you for your help!
Edit: the problem is similar to this question with no accepted answer.
A way to this is to set up vertical autolayout constraints like this (but you will need a reference to the actual bottomMargin constraint to be able to modify it) :
"V:|[scrollView][textField]-(bottomMargin)-|"
The first time you arrive on the screen, bottomMargin is set to 0.
Then when keyboardWillShow is called, get the keyboard frame (cf How to get height of Keyboard?)
func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
}
}
And animate the constraint bottomMargin to get the height of the keyboard (the duration is 0.3 after some tests, but you can adjust it) :
bottomConstraint.constant = keyboardHeight
UIView.animate(withDuration: 0.3, delay: 0, options: nil, animations: {
self.view.layoutIfNeeded()
}
That means that every time the keyboard will appear, an animation will move up the text field, hence the scroll view height will be smaller and everything will fit in the screen.
!! Don't forget to test it on landscape mode if you support it, and on iPad too!!
Finally, handle the case when the keyboard will disappear in the keyboardWillHide and set bottomMargin back to 0 :
func keyboardWillHide(_ notification: Notification) {
bottomConstraint.constant = 0
UIView.animate(withDuration: 0.3, delay: 0, options: nil, animations: {
self.view.layoutIfNeeded()
}
}

Swift 3 : Issue with cursor of textView when touched by the user

I've been struggling with a rather stupid issue for a few days now and I have not been able to find a solution.
I have a view controller with the following hierarchy: xcode screenshot
The user will have the possibility to add pictures below the textView (I haven't implemented this feature yet).
I set the "scrolling enabled" property of my textView to false so that its height automatically increases or decreases depending on the content typed by the user, which is working as expected.
I use the notification center in order to change the scrollView's contentInsets when the keyboard is shown like so:
func keyboardWillShow(notification: NSNotification) {
var userInfo = notification.userInfo!
var keyboardFrame: CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInsets: UIEdgeInsets = self.scrollView.contentInset
contentInsets.bottom = keyboardFrame.size.height + 3*self.postContentTextView.font!.lineHeight
self.scrollView.contentInset = contentInsets
}
func keyboardWillHide(notification: NSNotification) {
let contentInsets = UIEdgeInsets.zero
self.scrollView.contentInset = contentInsets
}
with the following observers in viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
I also set the scroll view content size in the viewDidLayoutSubviews function:
override func viewDidLayoutSubviews() {
self.scrollView.contentSize = self.postContentTextView.frame.size
}
Everything seemed to work fine until I realize that when the text is "too long" (or maybe it is something else), I am not able to select the last lines of the textView when I touch them. Actually, when I touch the screen the cursor is supposed to appear on the line I have just touched. This works normally for three quarters of the textView but it looks like the textView does not detect my finger for the last lines (the bigger the height is, the worse this issue is).
At the beginning, I thought I had a problem with my screen but I tested it on the iOS Simulator and the same problem occurred.
I have to admit that I am completely clueless about it. Does any of you have an idea of what the problem could be ?
Have you checked its constraints? The UITextView might be outside the bounds of its superviews. Set clipsToBounds to superview & test it. If it is out of the superview bounds, it will not detect touch

UITextView - Scrolling to selected place

I have a Notes view controller with a large UITextView in it. When the keyboard is active, I've made sure the contentInset is adjusted so that the user can see what's being typed. This works well.
However, if the textView already has a large amount of text in it and the keyboard isn't active yet, when the user taps on text in the lower portion of the textView, the textView doesn't automatically scroll up to show their cursor. As soon as they start typing, the textView scrolls to the appropriate position, but I'd like the textView to scroll to the position of the cursor as soon as they tap in the textView.
Here's my code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWasShown), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillBeHidden), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(_ notification: Notification) {
let mainViewY = self.view.frame.origin.y
let textViewY = self.textView.frame.origin.y
let oneLineHeight = self.textView.font.lineHeight
let delta = (textViewY - mainViewY) - oneLineHeight
let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
let keyboardHeight = (keyboardSize?.height)!
self.textView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight + delta, 0)
self.textView.scrollIndicatorInsets = textView.contentInset
}
func keyboardWillBeHidden(_ notification: Notification) {
self.textView.contentInset = UIEdgeInsets.zero
self.textView.scrollIndicatorInsets = UIEdgeInsets.zero
}
I searched for people asking this question but couldn't find anyone experiencing my particular issue.
How can I ensure that when the user taps the textView to begin typing in a portion of the textView that would be covered by the keyboard, the textView scrolls to show their cursor even before they actually type?
You can achieve this by calling scrollRangeToVisible (docs here) using the text view's selectedRange. That method scrolls the text view to any range of text, and the selectedRange should be at the position of the cursor.
I was having a similar issue, but I tried out your sample code.
In viewDidLoad, I commented out the second observer (self.keyboardWillBeHidden).
When I ran the simulator and selected any part of a large block of text, the textView did automatically scroll to the correct position.

iOS Swift 3 Keep TableView content at the right position when keyboard is shown

I have a Viewcontroller with a ContainerView and a TextField (looks like your typical chat app). Inside the ContainerView is an embedded TableView. I have implemented an observer function to increase the bottom layout constraint of the TextField, making the TextField and the ContainerView move up with the Keyboard.
The problem is the offset / inset of the TableView. It looks like the keyboard hides the TableView.
How can i make the content of the tableView move with the containerView?
Here i have added some Screenshots:
This is the initial Chat View Controller.
Now when the Keyboard shows up, the TableView does not maintain its scroll position. It might look like the containerView is simply behind the Keyboard, but it actually adjusted the size.
Now when you scroll down, you see that the tableView does not maintain its scroll position.
you can use the observer UIKeyboardWillChangeFrame and change the contentOffset of the tableView.
Here's how it's done.
NotificationCenter.default.addObserver(self
, selector: #selector(keyboardWillChangeFrame)
, name: NSNotification.Name.UIKeyboardWillChangeFrame
, object: nil)
and here's the keyboardWillChangeFrame function
func keyboardWillChangeFrame(_ notification: Notification) {
let beginFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let endFrame = ((notification as NSNotification).userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let delta = (endFrame.origin.y - beginFrame.origin.y)
self.threadTableView.contentOffset = CGPoint(x: 0, y: self.threadTableView.contentOffset.y - delta)
bottomConstraint.constant = view.bounds.height - endFrame.origin.y
self.view.layoutIfNeeded()
}
I also got the idea regarding this with this article.
http://derpturkey.com/maintain-uitableview-scroll-position-with-keyboard-expansion/
Try this:
You can set the tableView contentInset to keyboard's height when the keyboard is shown and set it to 0 when keyboard is hidden.
You can do it using keyboard notifications : UIKeyboardWillShow, UIKeyboardWillHide
override func viewDidLoad()
{
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(_ notification : Notification)
{
let info = (notification as NSNotification).userInfo
let value = info?[UIKeyboardFrameEndUserInfoKey]
if let rawFrame = (value as AnyObject).cgRectValue
{
let keyboardFrame = self.contentTableView.convert(rawFrame, from: nil)
let keyboardHeight = keyboardFrame.height
var contentInsets : UIEdgeInsets
contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardHeight, 0.0)
self.contentTableView.contentInset = contentInsets
self.contentTableView.scrollIndicatorInsets = contentInsets
}
}
func keyboardWillHide(_ notification : Notification)
{
let contentInsets = UIEdgeInsets.zero
self.contentTableView.contentInset = contentInsets
self.contentTableView.scrollIndicatorInsets = contentInsets
}
Could you update the contentInset property on the table view to account for the keyboard height in the observer function you've implemented?
Alternatively could you update the bottom constraint of the ContainerView to account for the keyboard height?

Resources