UITableView scroll to the top of the cell - ios

I have an UITableViewController with 3 static cells. One with the height of 60.0 at the top and one with the same size at the bottom. The height of the mid cell is dynamic and so big, that all 3 cells together fill the whole screen.
The mid cell contains an UITextView which fills the complete cell.
My problem is that when I want to type something in that TextView the tableview automatically scrolls when the keyboard is rising and I don't see the top of the cell including the cursor anymore until I manually scroll back there.
Is there any way I can prevent the table view from scrolling like this automatically? Or tell it that it should scroll to the top of the cell so I see the cursor?
I've already tried to override the viewWillAppear(_:) method without calling the super method of it but then I can't scroll the tableview manually enough so I can't get to the last cell when the keyboard is visible.
I've also tried to scroll manually to the cell inside the textViewDidBeginEditing(_:) but it changed nothing. My method looked like this.
func textViewDidBeginEditing(textView: UITextView) {
let indexPath = NSIndexPath(forRow: 1, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
I've no idea what else I could try so I'd appreciate your help.

Well, you can set the contentInset of the tableView like this:
self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 80, right: 0)
So you can use the bottom to compensate the offsets that you want.

mainTableView.scrollsToTop = true
OR
mainTableView.setContentOffset(CGPointZero, animated:true)

Related

Sending heightForRow:atIndexPath height value after calculating it in-cell

I have a UITableView that has few cells with different heights. Each row's is dynamically adjusted according to the content it contains.
I have a custom UITableViewCell with a UICollectionView in it that should be resized according to how many cells it has (all the cells should be visible, without inside scrolling).
I also have a UITableViewCell with another UITableView that should be resized according to how many cells it has (all the cells should be visible, without inside scrolling).
The problem is that I don't have the contentSize of the collectionView and the tableView while heightForRow:atIndexPath gets called so I can't set the values to something right.
I've tried to set it to UITableViewAutomaticDimension and to set the cell.contentView.frame.size.height to the contentSize when it got set (added an observer on "contentSize") but then the cells were on top of each other (the collectionView was ontop of the tableView instead of above it).
The tableView code is a regular tableView code.
The collectionView code is:
self.collectionView.collectionViewLayout = UICollectionViewLeftAlignedLayout()
let collectionViewFlowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
collectionViewFlowLayout.estimatedItemSize = CGSize(width: 34, height: 50)
What is the best way to adjust the size of the tableView and the collectionView?
Thank you!
You can just ask UITableView to adjust its cell height again with this piece of code:
self.tableView.beginUpdates()
self.tableView.endUpdates()

Update cell height after it is rendered

I've set up a UITableViewCell with UITableViewAutomaticDimension
The TableViewCell has a UICollectionView embedded in it which is not scrollable but can have a variable height based on the content of the collectionview.
Right now what I've tried is the render the cell and assign the height constraint of the collectionview to a variable collectionViewHeightConstraint and then update the height once the collectionview is rendered in the layoutSubviews method
override func layoutSubviews() {
super.layoutSubviews()
self.collectionViewHeightConstraint?.constant = self.collectionView!.contentSize.height
}
This is what the collectionview constraints look like (using cartography) :
self.contentView.addSubview(self.collectionview)
self.contentView.addSubview(self.holdingView)
constrain(self.holdingView!, self.contentView) {
holderView, container in
holderView.top == container.top
holderView.left == container.left
holderView.right == container.right
holderView.bottom == container.bottom
}
constrain(self.collectionView!, self.holdingView) {
collectionView, containerView in
collectionView.top == containerView.top
collectionView.left == containerView.left
collectionView.right == containerView.right
collectionViewHeightConstraint = collectionView.height == collectionViewHeight
containerView.bottom == collectionView.bottom
}
But that does not seem to update the cell height.
Is there any way to update the same?
Edit
This is not a duplicate question as suggested by some people and the explanation of why is in the comments below.
Since the comment was too small a space, I'll put everything here:
Note: You don't actually have to set the height constraint in viewDidLayoutSubviews just somewhere you can be sure that the UICollectionView has been set and your layout has been setup properly on your whole screen! For example, doing it in viewDidAppear and then calling layoutIfNeeded() will also work. Moving it into viewDidAppear will only work if you have your UICollectionView setup before viewDidAppear is called i.e you know your UICollectionView dataSource beforehand.
Fix 1:
Try reloading the UITableView after setting the height and checking if the heightConstant != contentSize. Use this to check if the height of the UICollectionView is updated properly i.e.:
override func layoutSubviews() {
super.layoutSubviews()
if self.collectionViewHeightConstraint?.constant != self.collectionView!.contentSize.height{
self.collectionViewHeightConstraint?.constant = self.collectionView!.contentSize.height
//to make sure height is recalculated
tableView.reloadData()
//or reload just the row depending on use case and if you know the index of the row to reload :)
}
}
I agree with your comment and that it is messy, I meant use that as a fix and/or to check if that is where the problem lies actually!
As for why it is 0, that happens probably because your UICollectionView hasn't been set yet (cellForItem hasn't been called yet) so contentSize isn't actually calculated!
Fix 2:
Once your dataSource for the UICollectionView has been set, that is you receive the data, you calculate the height the UICollectionView contentSize will have manually and set it once and reload the row. If the calculation is a tedious task, just set the dataSource and call reloadData on UICollectionView. This will ensure the UICollectionView is setup properly and then set the constraint of the cell to be the contentSize and call reloadData or reloadRow on the UITableView.
You basically can set the heightConstraint anytime after your UICollectionView has been setup and your view has been laid out. You just need to called tableView.reloadData() afterwards.
You can reload particular cell of tableview
let indexPath = IndexPath(item: rowNumber, section: 0)
tableView.reloadRows(at: [indexPath], with: .top)
Going by your requirement, I guess if we load the collectionview first and then load the tableview with the correct height of the collectionview, we can solve this.
collectionView.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.collectionViewHeightConstraint?.constant = self.collectionView!.contentSize.height
})
tableview.reloadData()
By this when tableview loads the cell has the desired height based on the collection view content size.

How to make UITableView content scroll around a central point

I'm trying to figure out how to make the content of a UITableView "centered" around the region contained by the two bars on each side of the screen so that it works on devices of all screen sizes. What I mean by this is:
The initial state of the view should have the first cell inside that region
The table view should be able to scroll until the last cell is inside that region
Cells should not disappear if they're still on screen
I've tried a number of different things around content offsets and insets of the tableview, as well as adjusting the table view's frame, but I inevitably either get the insets wrong or end up with cells disappearing before they're off screen.
I'm wondering if either this is the wrong approach altogether, or I just have the incorrect combination of settings.
Any tips would be greatly appreciated!
The following works for me:
//In viewDidLoad, etc...:
self.edgesForExtendedLayout = UIRectEdgeNone;
self.automaticallyAdjustsScrollViewInsets = YES;
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let inset = (self.tableView.frame.size.height - 44) / 2.0 // Figure out your bar size here
self.tableView.contentInset.top = inset
self.tableView.contentInset.bottom = inset
let path = NSIndexPath(row: 0, section: 0) as IndexPath
self.tableView.scrollToRow(at: path, at: .middle, animated: false)
}

UICollectionViewScrollPosition not working

i have a collectionview that i am trying to scroll programatically. the problem being that if the cell i want to scroll to is visible in the collection view it doesn't scroll it to the centre. so for the image below the lower cell is item 1. and it does not scroll to it but it will scroll past item 1 to item 2.
i have been trying to use UICollectionVieScrollPosition.CenterVertically but this does not seem to work.
self.collectionView?.scrollToItem(at: IndexPath(row: 1, section: 0), at: UICollectionViewScrollPosition.centeredVertically, animated: true)
is there a way around this to force the scrolling of cells that are visible to the centre of the collection?
the best way i found to do this is to not use scrollToItem but to get the CGRect of the index and then make that visible.
let rect = self.collectionView.layoutAttributesForItem(at: IndexPath(row: 5, section: 0))?.frame
self.collectionView.scrollRectToVisible(rect!, animated: false)
I'm trying to delay it with 0.1s. For my case, looks good for now:
collectionView.collectionViewLayout.invalidateLayout() //just in case, iOS10 may crash btw.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
Update:
ok, turns out that I'm using layout.estimatedItemSize and autolayout to calculate the width of my cells, that's why I have this problem.
That says, for me, it's because of CollectionView's dynamic sizing.
After I back to calculate the width manually, everything works fine. (by using -collectionView:layout:sizeForItemAt:)
First of all you need to find the center of the CollectionView and this is how it can be done:
private func findCenterIndex() -> Int {
let center = self.view.convert(numberCollectionView.center, to: self.numberCollectionView)
let row = numberCollectionView!.indexPathForItem(at: center)?.row
guard let index = row else {
return 0
}
return index
}
then get the row number under your scrollToItem and it should work:
collectionView.scrollToItem(at: IndexPath(item: findCenterIndex(), section: 0), at: .centeredVertically, animated: false)
call it from viewDidLayoutSubviews()
If itemSize is too small,scrollToItemnot working.
siwft
collectionView.contentOffset = offset
I use this fixed
It seems that you have centred the first cell in your UICollectionView vertically.
I have found that if I centred the first cell by adding an inset through the contentInset property of UICollectionView, its scrollToItem(at:at:animated:) method also doesn't work for cells already visible.
However, if I centred the first cell by adding an inset through the sectionInset property of UICollectionViewFlowLayout, then scrollToItem(at:at:animated:) works for visible cells.
Specifically, in code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Padding needed to allow the first and last cells to be centred vertically.
let insectHeight = (collectionView.bounds.height - collectionViewFlowLayout.itemSize.height) / 2.0
// This way is disabled because scrollToItem doesn't work for visible cells.
// collectionView.contentInset = UIEdgeInsets(top: insectHeight,
// left: 0,
// bottom: insectHeight,
// right: 0)
// This is the way for scrollToItem to work for visible cells.
collectionViewFlowLayout.sectionInset = UIEdgeInsets(top: insectHeight,
left: 0,
bottom: insectHeight,
right: 0)
}
My guess is that contentInset is a property UICollectionView inherited from UIScrollView, and the way scrollToItem(at:at:animated:) works out the offset seems incompatible to the way contentInset is used by UIScrollView.

How to change the content offset of a UICollectionView simultaneously?

I have a horizontal scrollable collectionView with two cells that take up the screen. Inside those cells is an embedded collection view that scrolls vertically. Is there a way for me to scroll the vertical CV inside the first cell, while using its ContentOffset to scroll the vertical CV in the second cell?
Here's how I'm trying to accomplish it:
let scrollView = notification.userInfo!["scrollView"] as! UICollectionView
if scrollView.contentOffset.y <= 250.0 {
childViewControllerForPosts.collectionViewForGroups?.collectionView.setContentOffset(CGPointMake(0, scrollView.contentOffset.y), animated: false)
self.topVerticalConstraint?.constant = -scrollView.contentOffset.y
}
else {
if self.topVerticalConstraint?.constant > -250 {
// Make sure it stays at -250
self.topVerticalConstraint?.constant = -250
}
}
The FeedCell would be the main collection view (which horizontally scrolls) that contains the vertical scrolling collection view. I'm trying to access the second one to change the vertical contentOffset when I scroll the one in first one.
Here's how I'm trying to save the variable that should contain the second main collection view:
collectionViewForGroups = collectionView(collectionView!, cellForItemAtIndexPath: NSIndexPath(forItem: 1, inSection: 0)) as? FeedCell
EDIT:
Note that when I scroll horizontally, the vertical scroll view didn't change. But I want it to change with the one I just scrolled in the gif. If I were to scroll the 2nd vertical collection view, the UIView above will reappear because the topLayConstraint changes due to the vertical contentOffset change.

Resources