uicollectionview cells are changing size after being displayed during transitions - ios

Currently i have a chat app utilising a collection view with a input accessoryView at the bottom to type in your messages. when the user types inside the textView, the keyboard is displayed and also dismissed by tapping outside the textView or interactively dismissing the keyboard.
the problem is that when the keyboard is dismissed, the collectionView cells (which contain the messages) correctly move to their new position, however they grow horizontally as they appear with that growth visible to the user. I have identified that this effect occurs during the keyboard animation which I require. Just don't know how to have the collectionViewCells resize in a way that is not visible. The following is the code I am using for the keyboard transition. Any help would be greatly appreciated.
UIView.animateWithDuration(animationDuration, animations: { () -> Void in
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, intersectionOfKeyboardRectAndWindowRect.size.height, 0)
self.collectionView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, intersectionOfKeyboardRectAndWindowRect.size.height, 0)
})
I think I may need to either invalidate the collectionView layout or layoutIfNeeded but not sure where to do that.
Thank you

Related

UIScrollView - Disabling isScrollEnabled drags the whole view upwards

I have a UIScrollView with a couple of subviews, one of them is a UIStackView that can be hidden/displayed when a button is pressed.
When the UIStackView is hidden I want to disable scrolling on the UIScrollView since the content will never have enough height that justifies scrolling being enabled. To achieve that, I'm using:
isScrollEnabled.toggle()
Problem is, when doing this, the whole view gets dragged up, as if I was scrolling down, causing certain elements to be hidden behind the navigationBar and the statusBar.
I tried to fix that using the following piece of code:
if let navigationBarHeight = vcDelegate?.navigationController?.navigationBar.frame.height {
if let statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height {
let totalHeight = navigationBarHeight + statusBarHeight
setContentOffset(.init(x: 0, y: -totalHeight), animated: true)
}
}
And that does actually work correctly when the contentOffset.y is >= a certain value that I can't precisely specify (because, for some reason, printing the value of contentOffset.y only shows a value that is != 0 when I scroll all the way to the bottom of the view.)
When the contentOffset.y is < than that mystery value, disabling the scroll will cause the following behavior:
The view will be dragged up
The view will be dragged down (because I call the setContentOffset function)
My bottom line question is: why does the view get dragged up when I disable scrolling? Could it be a constraint issue, some "background" offset change that I should be aware of or something else?

Keep Bottom of TableView Visible When Keyboard Shows With Autolayout in Swift

I have a tableview with a textview for entering text immediately below it similar to Apple Messages. When the user begins to enter text and the keyboard appears, I want the following behavior similar to IOS Messages.
If the keyboard will not cover anything, the visible part of the tableview remains unchanged.
If the keyboard will cover something, the tableview moves up just enough so that its bottom-most filled cell is just above the keyboard.
Because I'm using autolayout, I currently have a constraint between the tableview and the textview below it. Also, the project has IQKeyboard which manages a lot of other views involving textfields and textviews.
The constraint combined with IQKeyboard accomplishes 2. When the keyboard appears, the keyboard pushes the textview up. The textview pushes the tableview up. So if the tableview is fully populated, you see the last cell of the tableview above the textview above the keyboard as desired.
However, 2. is not working.
if the tableview is not filled, the keyboard pushes up the textview which pushes up the tableview so that you longer see the top of the tableview.
I have tried adjusting the contentOffset property of the tableview when the Keyboard Shows and this sort of works but the tableview initially moves up before coming back down. I think this is because the notification to change the offset property does not fire until after the keyboard has begun to move up.
I also tried adjusting the tableview height to its content but this causes the textview to expand to fill the difference due to constraints.
Content offset approach - problem is that content offset adjusts too late
//register for keyboard notifications and in handler:
if let infoKey = notification.userInfo?[UIKeyboardFrameEndUserInfoKey],
let rawFrame = (infoKey as AnyObject).cgRectValue {
let keyboardFrame = view.convert(rawFrame, from: nil)
self.heightKeyboard = keyboardFrame.size.height
UIView.animate(withDuration: 0.2, animations: {
self.tableView.contentInset = UIEdgeInsetsMake(self.heightKeyboard!, 0, 0, 0);
})
}
Can anyone suggest a way to mimic the behavior of Apple Messages? Thanks in advance for any suggestions.
One approach:
constrain the top of the tableView to the top of the view
constrain the bottom of the tableView to the top of the textField
constrain the bottom of the textField to the bottom of the view
create an #IBOutlet for the textField's bottom constraint
When the keyboard is shown, change the .constant of the textField's bottom constraint to the height of the keyboard view.
This will move the textField up, and because it's top is constrained to the bottom of the tableView, it will also move the tableView's bottom edge up.
Then scroll to the bottom of the tableView.
Layout:
Initial hierarchy, with 20 rows (scrolled to the bottom):
Hierarchy view (tableView background color set to green, so we can see its frame):
View after the keyboard is shown:
Hierarchy after the keyboard is shown:
Little tough to see from static screen caps, but the frame of the green rectangle (the tableView background) is now shorter... the user can still scroll up and down to see all the rows, but the bottom of the tableView is still constrained to the top of the textField.
When you the keyboard is dismissed, set the .constant of the textField's bottom constraint back to Zero.
You can see a full, working example project up on GitHub: https://github.com/DonMag/KBAdjust

UITableView tap not working first time after constraints change

IMPORTANT: My problem is not that I'm implementing didDeelectRowAt instead of didSelectRowAt. Already checked that :)
I have a UITableView that is shown on part of the screen in a modally presented view controller. When the user is dragging it resizes to full screen and back to some defined min height. I'm doing this by implementing the following methods from the UIScrollViewDelegate:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard !scrollView.isDecelerating else { return }
let contentOffset = scrollView.contentOffset.y
if tableViewHeightConstraint.constant < view.frame.height && contentOffset > 0.0 {
tableViewHeightConstraint.constant = min(contentOffset + tableViewHeightConstraint.constant, view.frame.height)
scrollView.contentOffset.y = 0.0
return
}
if tableViewHeightConstraint.constant > minViewHeight && contentOffset < 0.0 {
tableViewHeightConstraint.constant = max(tableViewHeightConstraint.constant + contentOffset, minViewHeight)
scrollView.contentOffset.y = 0.0
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Here I have some calculations if depending the dragging end position and the velocity the end size should be full screen or `minViewHeight`
// After calculating what the end size should be I'm animating the size change
heightConstraint.constant = newConstraintHeight
UIView.animate(withDuration: TimeInterval(calculatedAnimationDuration), delay: 0.0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Everything about the resizing and the scrolling works fine, but there is a problem that I cannot figure out why it's happening. It's the following:
When the view controller with the table view is shown for the first time with the min height and I tap on a cell it works fine.
If I drag to expand the table view to full screen height and tap on a cell, again it works fine.
If I drag to expand the table view to full screen height and then drag again to return it to the min height and then tap on a cell, nothing is happening, no UIScrollViewDelegate or UITableViewDelegate method is called at all. If I tap once more on a cell everything works fine.
One thing that I noticed is that after dragging the table view back to the min height the scroll indicator does not hide. On the first tap it hides, and on the second tap the didSelectRowAt is called.
UPDATE:
Here is a test repo for the problem: https://github.com/nikmin/DragTest
Please don't mind if the dragging doesn't work perfectly, I just put something so anyone can try it out, but I think the problem is easily reproducible.
Also one more thing... If you drag from full size all the way to the bottom so the table view reaches min height and you continue dragging so the content offset is < 0 and the you release, the problem is not happening.
Drag TableView to return it to the min height and then tap on a cell, nothing is happening because:
When you drag to expand the table view to full screen, scrollView.isDecelerating is true. So the code inside scrollViewDidScroll method will run.
But when you drag TableView to return it to the min height, scrollViewDidScroll is false. So the code inside scrollViewDidScroll method won't run. It's make the first tap do nothing.
Simply remove guard !scrollView.isDecelerating else { return } from scrollViewDidScroll. You will tap cell normally after drag TableView down.
But you need change logic a little, animation will go wrong after remove above line.
Hope it can help you ;)
After trying to figure out a solution to this without result, we (me and a UX designer) decided to change the behaviour a bit.
So in the real scenario in the app I'm implementing this in, the table view is inside another view that has also a title label and some other views above the table view. We decided to add a pan gesture recognizer to this root view and disable the scrolling of the table view when the view has the min size. This way the pan gesture recognizer will take over whenever the user tries to drag anywhere inside the view (including the table view), so the expanding of the view works. And the tap in the cell still works.
When the view has the max height the table view scroll is enabled so the user can scroll. The downside of this approach is that when the user scrolls to the top of the table view and continues scrolling the view will not decrease the size. But he still has the option to drag it down by dragging any of the views above the table view. When dragging down in this way, only the size of the table view changes, and the content offset isn't, which is the root of the problem (changing both at the same time).
It looks like incorrectly set content offset. The first touch cancels incorrect position(unscroll), this is why it is not registered. It might be better if we got an access to the full code to check it, because I can't tell you where exactly the problem lies, but I guess it is in method scrollViewDidScroll.

Tableview not scrolling on the first tap iOS

I have views hierarchy in Viewcontroller below. First time when I open the view controller my tableview is not scrolling at all even when I tap on tableview it will not recognize my tap. After that It is working fine as expected. I can able to scroll and select cell and working fine. Only first time I am having issue.
I don't have enough reputation so I can't post image.
ViewController -> View -> ScrollView -> View(viewSC) -> TableView -> TableViewCell
Anyone experiencing this kind of behavior.
Thanks in advance.
EDITED
self.scrollView.contentSize = self.viewSC.frameSize;
self.scrollView.contentInset = UIEdgeInsetsMake(self.HV.frameHeight, 0, 0, 0);
self.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(self.HV.frameHeight, 0, 0, 0);
this is the only code added in viewDidLoad. Other than that it is all working fine.
Are you trying to scroll as soon as the table view is created? When it comes to UITableView manipulation, you have to make sure that it's created and displayed before you try to scroll it. UITableView is picky like that. If you are loading your view and then you want to scroll the table view, call your code in viewDidAppear, not viewDidLoad.

Best way to animate the index of a UITableView?

I have a UITableView with a UIToolbar-like view at the bottom of the screen. I'd like to dynamically animate the toolbar to slide up and down to appear and disappear on the screen when the user takes certain actions on the table data. The problem I'm encountering is that when I animate the toolbar upward, it covers the last few letters of the index.
I'd like to shrink the index size as an animation, along with the toolbar animation. The standard UITableView index functionality doesn't provide us access to this view, just what the view displays, via sectionIndexTitlesForTableView. What's the best way to go about modifying it in such a way?
One way is to animate the entire table view height. This will also prevent your toolbar from covering basically the last cell in case they wanted to do something with it while the toolbar is up.
If you have a UITableViewController then you might have to move your code into a normal UIViewController.
Another way is to enumerate through the subviews of your table view and find the section title view that way, though I'm not sure if that would work very well.
Remember that UITableView is just another scrollview. Just adjust the contentInset and scrollIndicatorInsets according to your toolbar's height:
UIEdgeInsets contentInset = self.tableView.contentInset;
contentInset.bottom = self.myToolbar.frame.size.height;
self.tableView.contentInset = contentInset;
UIEdgeInsets scrollInset = self.tableView.scrollIndicatorInsets;
scrollInset.bottom = self.myToolbar.frame.size.height;
self.tableView.scrollIndicatorInsets = scrollInset;

Resources