3

This isn't a question about raising the collectionView when the keyboard rises and lowers, that part works fine. This is about the scrollView inside the collectionView not rising with it.

I have a view pinned to the bottom of the screen and inside that view I have a textView. Pinned to the top of the view is a collectionView that's pinned to the top of the screen

-top of viewController
    collectionView
    containerView that contains a textView
-bottom of viewController

When the textView's textField is tapped I use a notification which detects when the keyboard rises and lowers. It works fine because the collectionView goes up and down like it's supposed to. The problem is the collectionView's contents. If the collectionView's cells fill the screen when the collectionView rises up it's scrollVIew doesn't raise with it so the the cells are behind the keyboard. 2 pics below.

When the keyboard notification is sent I tried changing the collectionView's contentInset, scrollIndicatorInsets, and tried scrolling to the last cell but nothing. The cells just scroll up a tad bit.

@objc fileprivate func keyboardWillShow(notification: Notification) {

    guard let keyboardDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
    guard let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }

    let keyboardFrame = keyboardFrame.cgRectValue
    let keyboardHeight = keyboardFrame.height

    containerViewBottomAnchorConstraint?.isActive = false
    containerViewBottomAnchorConstraint = containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -keyboardHeight)
    containerViewBottomAnchorConstraint?.isActive = true

    // I tried this
    let item = tableData.count - 1
    let indexPath = IndexPath(item: item, section: 0)
    if !tableData.isEmpty {
        collectionView.scrollToItem(at: indexPath, at: .bottom, animated: true)
    }

    // I tried this
    let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: -keyboardHeight, right: 0)
    collectionView.contentInset = contentInsets
    collectionView.scrollIndicatorInsets = contentInsets

    UIView.animate(withDuration: keyboardDuration, animations: {
        self.view.layoutIfNeeded()
    }) { [weak self](_) in
        self?.autoScrollToLastCell() // I tried this
    }
}

@objc fileprivate func keyboardWillHide(notification: Notification) {

    guard let keyboardDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }

    containerViewBottomAnchorConstraint?.isActive = false
    containerViewBottomAnchorConstraint = containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0)
    containerViewBottomAnchorConstraint?.isActive = true

    UIView.animate(withDuration: keyboardDuration, animations: {
        self.view.layoutIfNeeded()
    }) { [weak self](_) in
        self?.autoScrollToLastCell()
    }
}

func autoScrollToLastCell() {
    let section = 0
    let item = collectionView.numberOfItems(inSection: section) - 1
    let index = IndexPath(item: item, section: section)
    if item > 0 {
        collectionView.scrollToItem(at: index, at: .bottom, animated: true)
    }
}

Before the keyboard rises enter image description here

After the keyboard rises the collectionView is up but it's contents aren't enter image description here

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

3 Answers3

4

This should be the accepted answer:

Add the listener when the keyboard is raised:

NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)

Then add this function:

@objc func handleKeyboardWillShow(notification: Notification) {

            collectionView.scrollToItem(at: IndexPath(row: messagesList.count - 1, section: chatSection), at: .top, animated: false)
    }

Note: If you have more than one section, change the section value

Andrey Solera
  • 2,311
  • 2
  • 26
  • 51
  • I’ll try it tomorrow but explain to me why my answer is in correct when I’ve used it with 1 message that says “hello” to 50 messages that have different cells sizes and I haven’t had 1 problem with my answer. If what you’re saying my answer is incorrect then I should have all sorts problems but I don’t have any. How is that possible? – Lance Samaria Aug 10 '19 at 02:05
  • Someone remove one of my original comments. I’ll leave again – Lance Samaria Aug 10 '19 at 02:06
  • Your answer might be correct for your particular situation, because what you needed was indeed the padding, however, the original question was formulated on scrolling the collection view when the keyboard is raised – Andrey Solera Aug 10 '19 at 02:08
  • @@Andrea please read my answer, I never said the 8 point padding is what fixed the issue. Look at the very first sentence, it says “all I had to do was set the collectionView’s content inset”. The second sentence says “8 points just so it appears that the last cell can have padding”. I used the word “appears”. I never said the 8 points is what fixed my issue. – Lance Samaria Aug 10 '19 at 02:13
  • No your incorrect about that because even if the contentInset is 0,0,0,0 it works fine. The issue was when the keyboard rose the content was still underneath the keyboard. Once I set the contentInset the content was no longer behind the keyboard it was where it was supposed to be. The padding just made it rise. Without it without the padding it worked. Why do you keep saying the padding fixed the issue? – Lance Samaria Aug 10 '19 at 02:16
0

Changing to keyboardDidShow might solve the problem because keyboardWillShow is called when the keyboard is showing up.

NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil)

Adding the function will help to scroll the view to the bottom.

@objc func keyboardDidShow() {
    self.collectionView.scrollToItem(at: [0, messageList.count - 1],
                                     at: .bottom,
                                     animated: false)
}
Swee Kwang
  • 724
  • 9
  • 15
-3

The fix was easy, all I had to do was set the collectionView's content inset to UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0)

Below I set the bottom argument to 8 just so it appears that the last cell can have padding between its bottom and the the containerView's top

collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0)

For clarity setting the collectionView’s contentInset property is what fixed this problem. It has nothing to do with the 8 points. Even it was 0,0,0,0 it worked fine. The 8 points was just for appearance/preference. There aren’t any “magic numbers” used here to fix anything.

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • you shouldn't add a magic number for this fix, since the text message could be larger than that given size – Andrey Solera Aug 09 '19 at 04:56
  • And what does the text message itself have to do with my question? The text message itself isn't what was causing the problem, it was the collectionView's contentInset that was causing it. My question doesn't mention anything about a text message. Please read the Apple docs on what contentInset means: https://developer.apple.com/documentation/uikit/uiscrollview/1619406-contentinset Notice it says plain as day "The custom distance ...". If it's custom you can put whatever you want there. – Lance Samaria Aug 09 '19 at 09:54
  • If you can downvote I'd assume you should easily be able to provide an answer that 1. shows my "magic number" is incorrect and the problems it will cause, 2. your answer is the correct way to do it to prevent the mishap that my question was referencing, and 3. disprove the apple docs that says contentInset should't be a "custom distance" or a "magic number" and should be whatever number comes from your answer. If you do that I will accept your answer AND upvote it. If you can't do those 3 things then you should remove the downvote because it wasn't valid downvote. – Lance Samaria Aug 09 '19 at 10:02
  • It's not the correct answer to your question, since you have explicitly asked: "How to scroll the collectionView's contents when the keyboard is raised", that means that you need the content to be scrolled when the keyboard is up, by adding a padding your question is not answered. If that was your solution, your question, then is not formulate properly – Andrey Solera Aug 09 '19 at 13:44
  • You misread my answer. I never padding was the answer to my question, I said setting the contentInset is what fixed my answer and the 8 point padding was just for appearance. Reread what I initially wrote. You based your comments AND downvote on my answer thinking I said padding is what fixed the issue, it wasn’t padding it’s the contentInset and it works fine. Since you downvoted me based on padding and I never said that was what fixed the issue you should remove your downvote. – Lance Samaria Aug 10 '19 at 02:10
  • Your provided solution is not the correct answer to your question. – Andrey Solera Aug 10 '19 at 02:13
  • Then why does it work for me? How can you say it’s not correct when it works fine? That makes no sense – Lance Samaria Aug 10 '19 at 02:16