UITableView + Add content offset at top - ios

I need to add some blank space to the top of my UITableView that does not affect the size of the content area. Shifting the content down or adding a blank cell is NOT what I want to do. Instead I just want an offset.
How?

I'm not sure if I'm following you but I think I'm having the same predicament. In my case I must give some space to the ADBannerView at the top of the screen so what I did was in the viewDidLoad method I added:
[self.tableView setContentInset:UIEdgeInsetsMake(50,0,0,0)];
the values it takes are UIEdgeInsetsMake(top,left,bottom,right).
Alternatively the same with Swift:
self.tableView.contentInset = UIEdgeInsetsMake(50, 0, 0, 0)
Swift 4.2:
self.tableView.contentInset = UIEdgeInsets(top: 50, left: 0, bottom: 0, right: 0)

Swift 5.1
add the following in viewDidLoad
tableView.contentInset.top = 100
Really that's all there is to it.
override func viewDidLoad() {
super.viewDidLoad()
tableView.contentInset.top = 100
}

You can add an "empty" header view to the table... this would give the initial appearance of the table to have an offset, but once you started scrolling the offset would be gone. NOt sure that's what you want.
If you need a permanent offset and are not already using section headers, then you could create the offset similarly to above by making custom views for the section headers, especially if you just have one section, this could give you the look of a permanent offset.
I can post sample code if it sounds like either of those are what you are looking for.

I combined Jigzat's answer with:
[self.tableView scrollRectToVisible:CGRectMake(0, 0, 320, 1) animated:NO];
in
- (void)viewDidLoad
so the first cell isn't at the top.

Sounds like you want to wrap a 'View' around your UITableView. If you have a UITableViewController in IB the UITableView will automatically be set to the view of UITableViewController. You change view property to a normal UIView and add your UITableView in there and give it a offset.
---Edit---
I just read my post and thought it made little sense :) When you create a UITableViewController you get this (in pseudo code):
UITableViewController.view = UITableView
This means that the actual table will take up the whole space and you cannot even add other views. So you need to change the
UITableViewController.view = UIView
and add your table to that UIView

I combined this answer with this one:
https://stackoverflow.com/a/9450345/1993937
To make the tableView appear at the top of the content inset, so the space at the top isn't cut off by having the tableView scrolled down slightly when the view initially appears. (18 is my top gap)
[self.tableView setContentInset:UIEdgeInsetsMake(18,0,0,0)];
[self.tableView setContentOffset:
CGPointMake(0, -self.songListTable.contentInset.top) animated:YES];

Related

Setting a minimum content size on a UITableView

I have a view at the top of a view controller, and a tableview underneath it.
I've made it such that as the tableview is scrolled up the top view scrolls up too, up to a maximum amount, of lets say 50 points.
The tableview also has a top inset of 50:
tableView = UIEdgeInsets(top: 50, left: 0, bottom: 0, right: 0)
... so that it's cells start below the top view.
And in the scrollview delegate there is some code along the lines of:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollViewYOffset = ...
topViewHeightConstraint.constant = max(minTopViewHeight, minTopViewHeight - scrollViewYOffset)
}
This ensures as the user drags down on the tableview the top view 'sticks' to it, and is also pushed up when the user pushes the table view up.
I've drawn this picture to better describe what the Storyboard looks like:
So far so good. When there are a lot of cells and the user scrolls the table view up, the top view remains at it's minimum height nicely.
But if you are scrolled up - so the top view is at it's minimum - then the number of cells are reduced, the top view pings back down to it's maximum height.
This is because the actual content size of the tableview has dropped below its bounds height, and so as a scrollview it brings the top of the content to the top of the scrollview again (plus the 50 point top inset of course).
I would like to be able to scroll the tableview up, so the top view remains at its minimum height, regardless of the number of cells it contains - i.e. regardless of it's content size.
Can anyone think of a clever way to set a minimum content size on the table view?
(So far I've tried messing around with the footer, having a cell at the bottom that is essentially a spacer - this messes up the tableview's logic and some reordering code I have in there. I've attempted to coerce the offers etc. to my will, but haven't quite worked out how to achieve this.)
I would greatly appreciate some UI genius to point me in the right direction :) Thank you.
UPDATE:
Thank you for all the answers and comments.
After trying various types of footer and header views, tweaking constraints & layout priorities on scroll, adding spacer cells, putting the tableview inside a scrollview, etc. - it finally occurred to me I was making this more complicated than I needed to, and should just update the cause of the problem on the scrollViewDidScroll, the contentInset value.
See the answer below for a code example that achieves the behaviour I was looking for.
As the contentInset is what is causing the tableview to ping back to the wrong point, I simply needed to adjust the content inset as the tableview was scrolled up/down.
Here is some example code of what I did:
let maxPointsTopViewCanMoveUp: CGFloat = 50
let topInset = abs(min(max(-maxPointsTopViewCanMoveUp, scrollView.contentOffset.y), 0))
scrollView.contentInset = UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)
scrollView.scrollIndicatorInsets = UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)
let amountToMoveTopViewUp = maxPointsTopViewCanMoveUp - topInset
topViewToSuperviewTopConstraint.constant = amountToMoveTopViewUp
This is called from the scrollViewDidScroll of the tableview.
It means that when there are too few cells in the tableview to fill the content, the top the tableview sticks in the place it had been scrolled up to (i.e. the amount it had pushed up the top view).
Some ideas that you can try:
Create a "dummy" row on index 0 and make it's height be 50 (it will be hidden below the top view). Maybe you can leave that dummy row in section 0 and the rest of your data in section 1, so you don't have to think about it when deleting your data.
Or, instead of a dummy row, you can set the height of the tableView's header to be 50
Maybe set a minimum height constraint on the tableView, and keep it's content compression resistance priority high.

How to remove UITableView top inset when embedded in ContainerView?

I am currently working on an iPhone App. I now have a problem with the top spacing inset of a UITableView. See this screenshot:
There should be no space between the table view cells and the buttons.
I do not know how to fix this. The UITableView is embedded in a ContainerView like this:
I think I got the container view constraints right. Top Space to Chapter Button is set to 0.
I tried to change some settings of the table view controller in storyboard. For example the Adjust Scroll View Insets. However it does not change anything when I disable that.
I also tried to set the TableView insets directly in the code in viewDidLoad():
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
However this also did not fix it.
Can anyone help? I have no idea where to look.
This can be solved by disabling auto adjustment of scroll view insets. However this setting has to be applied to the ViewController which contains the ContainerView (with the UITableView inside of it). It does not work when applied to the UITableView itself.
This can be done via Storyboard:
Or via code in viewDidLoad(): self.automaticallyAdjustsScrollViewInsets = false
Since iOS 15 there's a new parameter which can introduce a top gap when using a section header. This can be removed with the following:
if #available(iOS 15, *) {
tableView.sectionHeaderTopPadding = 0
}
Try this Hope this work.
hide View which contains the ContainerView.
self.automaticallyAdjustsScrollViewInsets = NO

Scroll in first table cell to center

I have a UITableView with custom cells. The cells are of a constant height 200. I want to animate the scrolling in of the first cell. This works using scrollToRowAtIndexPath. However, the row appears at the top of the view, whereas I would like it to appear dead center in the visible tableView. I tried setting the tableView's contentInset property to a bunch of different values, but none seem to provide the right offset. Any suggestions?
UITableView actually has a neat method for this:
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath
atScrollPosition:(UITableViewScrollPosition)scrollPosition
animated:(BOOL)animated
You can use it to scroll the tableView to the point where the cell is at the center of the tableView like this:
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:TRUE];
The only way I could find to do this was to use scrollRectToVisible instead of scrollToRowAtIndexPath. For whatever reason (possibly because scrolling on my table was disabled), the positioning was still off even using #Emil's suggestion of combining scrollToRowAtIndexPath with a contentInset.
This works:
tableView.contentInset = UIEdgeInsets(top: cellHeight, left: 0, bottom: cellHeight, right: 0)
tableView.scrollRectToVisible(CGRectMake(0, 0 - tableView.contentSize.height, cellWidth, tableView.contentSize.height), animated: false)

Adjust a UIScrollView height based on a UITableView

I have a UIScrollView which contains a UIView and a UITableView. My goal is to adjust the height of the UIScrollView to allow me to scroll the contents of the UIScrollView to a specific point.
Here is my view: It has a UIView up top and a UITableView down below.
When I scroll, I want the UIView to stop at a specific point like so:
The tableView would be able to continue scrolling, but the UIView would be locked in place until the user scrolled up and brought the UIView back to its original state.
A prime example of what I am trying to do is the AppStore.app on iOS 6. When you view the details of the app, the filter bar for Details, Reviews and Related moves to the top of the screen and stops. I hope this all made sense.
Thanks
I ended up going with a simpler approach. can't believe I didn't see this before. I created two views, one for the UITableView's tableHeaderView and one for the viewForHeaderInSection. The view I wanted to remain visible at all times is placed in the viewForHeaderInSection method and the other view is placed in the tableHeaderView property. This is a much simpler approach, I think than using a scrollview. The only issue I have run into with this approach is all my UIView animations in these two views no longer animate.
Here is my code.
[self.tableView setTableHeaderView:self.headerView];
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.tableViewHeader;
}
add yourself as a UIScrollViewDelegate to the UITableView and implement the - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView so that if your views are in their starter positions they do this:
- your UITableView animates its size to the second state:
[UIView animateWithDuration:.1f animations:^{
CGRect theFrame = myView.frame;
theFrame.size.height += floatOfIncreasedHeight;
myView.frame = theFrame;
}];
- your UIView animates its vertical movement
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void){
view.center = CGPointMake(view.center.x , view.center.y + floatOfVerticalMovement);
}completion:^(BOOL Finished){
view.center = CGPointMake(view.center.x , view.center.y - floatOfVerticalMovement);]
Finally always in the delegate implement – scrollViewDidScrollToTop: so that you know can animate back to the initial state (using the same techniques reversed).
UPDATE:
since your views are inside a scroll view, there is a simpler way if you are ok with the table view being partly out of bounds in your starter position (i.e. instead of changing size it just scrolls into view):
make the scroll view frame size as big as your final tableview + your initial (entire) view and place it at 0,0 (so its final part will be hidden outside of the screen)
scrollview.frame = CGRectMake(0,0,tableview.frame.size.width,tableview.frame.size.height + view.frame.size.height);
you make the container scrollview contents as big as the entire table view + the entire view + the amount of the view that you want out of the way when scrolling the table view.
scrollview.contentSize = CGSizeMake(scrollview.frame.size.width, tableview.frame.size.height + view.frame.size.height + floatOfViewHeightIWantOutOfTheWay);
you place the view one after the other in the scrollview leaving all the additional empty space after the table view
view.frame = CGRectMake(0,0,view.frame.size.width, view.frame.size.height);
tableview.frame = CGRectMake(0,view.frame.size.height, tableview.frame.size.width, tableview.frame.size.height);
now it should just work because since iOS 3 nested scrolling is supported
You can easily achieve this by setting the content size of the scrollView correctly and keep the height of the UITableView smaller than your viewcontroller's height, so that it fits the bottom part of the top UIView and the UITableView...
Another scenario is to split the top View in 2 parts.
The part that will scroll away and the part that will be visible.
Then set the part that will scroll away as the entire UITableView header and the part that will remain visible as the header view for the first table section.
So then you can achieve this with a single UITableView, without having to use a UIScrollView
What you're looking for is something like what Game Center happens to do with it's header which can actually be modelled with a table header, a custom section header view, and some very clever calculations that never actually involve messing with the frame and bounds of the table.
First, the easy part: faking a sticky view. That "view that's always present when scrolling the table" implemented as a section header. By making the number of sections in the table 1, and implementing -headerViewForSection:, it's possible to seamlessly make the view scroll with the tableview all for free (API-wise that is):
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
label.text = #"Info that was always present when scrolling the UITableView";
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor colorWithRed:0.243 green:0.250 blue:0.253 alpha:1.000];
label.textColor = UIColor.whiteColor;
return label;
}
Finally, the hard part: KVO. When the table scrolls, we have to keep the header up there sticky with regards to the top of the view's frame, which means that you can KVO contentOffset, and use the resultant change in value to approximate the frame that the view should stick to with a little MIN() magic. Assuming your header is 44 pixels tall, the code below calculates the appropriate frame value:
CGPoint offset = [contentOffsetChange CGPointValue];
[self.tableView layoutSubviews];
self.tableView.tableHeaderView.frame = CGRectMake(0,MIN(0,offset.y),CGRectGetWidth(self.scrollView.frame),44);
If the above is infeasible, SMHeadedList actually has a fairly great, and little known, example of how complicated it can be to implement a "double tableview". That implementation has the added benefit of allowing the "header" tableview to scroll with the "main" tableview.
For future visitors, I've implemented a much simpler version, albeit one that accomplishes the goal with Reactive Cocoa, and a little bit of a different outcome. Even so, I believe it may be relevant.
What if you break the UIView into the top and bottom. The bottom will be the info.
Set UITableView.tableHeaderView = topView in viewDidLoad
and the return bottomView as Section Header in delegate method to make it float:
(UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section
{
return bottomView;
}
Just using the UITableView can solve with your problem. it is not need to use another scroll view.
set your view as the header view of UITableView. Then add your present view to the header view.
complete - (void)scrollViewDidScroll:(UIScrollView *)scrollView; . Tn the function to check the contentoffset of scroll view, and set the present view's frame.

How to remove header and footer space from a UITableView?

I have a UITableView in the grouped style, and only one section. However there is some blank space above and below the table view that is shown when the user scrolls too far. How can I remove this blank space?
You can do this by altering the contentInset property that the table view inherits from UIScrollView.
self.tableView.contentInset = UIEdgeInsetsMake(-20, 0, -20, 0);
This will make the top and bottom touch the edge.
Add this code:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 0;
}
Actually this question answered my question.
Reducing the space between sections of the UITableView.
UIView can be inserted at the top and bottom of the table(drag and drop). Set their properties as transparent and height of 1 px. This is to remove the extra padding in front of the cells.
you can also use this code for removing space between first cell of uitableview..
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0.002f;// set this...
}
Uncheck Extend Edges Under Top bar.
This answer comes quite late, but I hope it helps someone.
The space is there because of the UITableView's tableHeaderView property. When the the tableHeaderView property is nil Apple defaults a view. So the way around this is to create an empty view with a height greater than 0. Setting this overrides the default view thereby removing the unwanted space.
This can be done in a Storyboard by dragging a view to the top of a tableView and then setting the height of the view to a value of 1 or greater.
Or it can be done programmatically with the following code:
Objective-C:
CGRect frame = CGRectZero;
frame.size.height = CGFLOAT_MIN;
[self.tableView setTableHeaderView:[[UIView alloc] initWithFrame:frame]];
Swift:
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
Comments
As others have noted you can use this same solution for footers.
Sources and Acknowledgements
See the Documentation for more details on the tableHeaderView property.
Thanks to #liushuaikobe for verifying using the least positive normal number works.
My original answer: https://stackoverflow.com/a/22185534/2789144
In my case issue was with the constraints i was applying. I have to change them in order to show 2 rows in my case while bottom of table touching last row.
Use the bounces property of UIScrollView:
[yourTableView setBounces:NO];
This will remove what seems to be an extra padding at the top and bottom of your UITableView.
Actually, it will just disable the tableview's scrollview to scroll past the edge of the content.

Resources