Reputation: 12336
Say you have a table view T which shows messages,
T[ message d
T[ message c
T[ message b
T[ most recent message down bottom
Say there are 100 messages, with the bottom 4 visible in the example.
So the table view height is 700 say. You have a typical text entry underneath...
T[ message d
T[ message c
T[ message b
T[ most recent message down bottom
[enter snapped chat message!] [send]
When the keyboard appears, the new height of the visible table view is 400 say.
Of course, that will "cut off the bottom" of the messages - the most recent two will no longer be visible.
T[ message d
T[ message c
[enter snapped chat message!] [send]
[ iOS keyboard ]
[ iOS keyboard ]
(So, messages A and B are now "under" the keyboard.)
Naturally what you do is just scroll the table after the keyboard appears, for example. No problem doing it in an ad-hoc manner.
However, it would be really natural if one could subclass UITableView in such a way that, as the size of the visible area changed, the table view knew to keep the "bottom point" identical.
So, as the bottom of the table moves up and down (due to keyboard appearing - or whatever cause), the table would scroll actually based on the movement of the "base" of the table.
(Apart from anything else this would solve the "match the animation timing" nuisance.)
Could this be achieved elegantly and if so how - indeed this would seem so natural these days, perhaps it's built-in to UITableView as a flag and I just don't know?
once again the question here is
(so as to keep the "bottom point the same")
Note that it's trivial to just scroll the table "manually" as it were from the outside.
Upvotes: 4
Views: 1322
Reputation: 9947
This is a way late answer, but I just solved the problem a bit more elegantly than the very ingenious accepted solution.
You'll need to have the following AutoLayout constraints (I'll omit the horizontal constraints):
MessageContainerView
is where I keep the message text view and button. Now, in your ViewController
add an IBOutlet
for the bottom constraint of MessageContainerView
:
@IBOutlet weak var messageContainerBottomConstraint: NSLayoutConstraint!
Next step is animating the bottom constraint's constant to be equal to the keyboard height + the safe area at the bottom and scrolling the table to the bottom when the keyboard is shown (add an observer for the notification as well):
@objc func showKeyboard(_ notification: NSNotification) {
guard let userInfo = notification.userInfo,
let targetFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
let animationTime = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
else {
return
}
let safeAreaBottom = view.safeAreaInsets.bottom
messageContainerBottomConstraint.constant = -targetFrame.cgRectValue.height + safeAreaBottom
UIView.animate(withDuration: animationTime) {
self.view.layoutIfNeeded()
self.tableView.scrollToBottom()
}
}
Note that we use UIResponder.keyboardAnimationDurationUserInfoKey
for the animation timing, so everything will be smooth.
Lastly, tableView.scrollToBottom()
is an Extension for UITableView
:
import UIKit
extension UITableView {
func scrollToBottom() {
let numberOfSections = self.numberOfSections
let numberOfRowsInSection = self.numberOfRows(inSection: numberOfSections - 1)
let indexPathForLastRow = IndexPath(row: numberOfRowsInSection - 1, section: numberOfSections - 1)
self.scrollToRow(at: indexPathForLastRow, at: .bottom, animated: false)
}
}
Let me know if you can try it, and if I missed anything.
(I did notice you asked for a way to modify the native UITableView
behavior instead of "manually" scrolling, but I think this is a valid way of solving the problem)
Upvotes: 2
Reputation: 6992
Section headers stick to the top, so maybe something like this:
1. Make the most recent message a section header view instead of a table cell
2. Mirror the table view vertically:
tableView.transform = CGAffineTransformMakeScale(1, -1);
3. Mirror vertically the section header and table cells
4. Reverse the order of your messages
Is that what you were looking for?
Upvotes: 4
Reputation: 1996
As far as I know, you have a few choices for doing this:
If you have the indexPath of the row you want to be visible:
tableView.ScrollToRow(indexPath, UITableViewScrollPosition.Top, true);
This will scroll to the cell given by the indexPath. In this case you are selecting the option to show it on the .Top
of the screen.
If you don't have the indexPath, you can use:
tableView.ScrollRectToVisible(new RectangleF(0, Ydisplacement, tableView.Width, tableView.Height), true);
Upvotes: 0