Scrolling ViewController Content - ios

What I have in the view controller view are :
An image of fixed height
Few labels
Table view with n rows.
Once rendered I want everything here to be inside the scroll the view so the user can scroll the entire screen as needed. Note that the scrollView needs to expand to the entire size of the tableView to show its full contents. I have tried different ways of doing this but unable to do it. I would appreciate any pointers or code segment to get this done.

There are essentially two ways to do so.
tableHeaderView
The first way involves the tableHeaderView property of the UITableView instance you have. You can simply add the UITableView with the constraints/frame/autoresizingMask that allows you to put it full-screen. Done that, you simply do (i.e. in your viewDidLoad):
UIView *headerView = [UIView new];
// Here I am supposing that you have a 200pt high view and a `self.tableView` UITableView
headerView.frame = CGRectMake(0, 0, self.tableView.frame.size.width, 200.0);
UIImageView *fixedImageView = [UIImageView new];
// configure your imageView..
[headerView addSubview:fixedImageView];
// configure labels as you want and add them to headerView as subviews
// Now set `UITableView` headerView
self.tableView.tableHeaderView = headerView;
If you want to use AutoLayout for your tableHeaderView, I suggest you to take a look at this question
Dynamic scrollView
Another way to do this is to to create an UIScrollView, put everything inside, and let it scroll. The downside of this method is that if you are using floating section headers for your UITableView, they will not float due to the fact that the tableView is going to stay fixed, while the parent scrollView is going to scroll.
On the other side, this approach is more AutoLayout friendly due to the fact you can use constraints easily.
To do so, you start adding an UIScrollView to your view, and placing all your other views inside it.
Be sure to add a Vertical Spacing constraint between the first view inside your scrollView (I suppose the UIImageView) and the scrollView top, and between the last view (I suppose the UITableView) and the scrollView bottom, to avoid an ambiguous content size.
You should have something like that (I omitted the labels for the sake of brevity):
Note that every view is inside a parent UIScrollView
After that, add an Height constraint to the tableView, and add an IBOutlet to your view controller subclass, i.e. like this:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *tableViewHeightConstraint;
Now you only need to configure this constraint to reflect the tableView natural height, given by its rows, etc. To do so, you simply calculate the height in this way:
// Resize TableView
CGFloat height = self.tableView.contentSize.height;
self.tableViewHeightConstraint.constant = height;
Now the tableView will resize, and due to its constraints it will adapt the parent scrollView contentSize.
Just be sure to refresh this height constraint anytime you reload the UITableView dataSource.

Related

Resize ScrollView based on TableView size

I'm trying to create layout that it structured like this:
- View
-- ScrollView
--- ContentView
---- CustomView
---- CustomView
---- TableView
---- CustomView
The tableView itself is auto-resizable using "invalidateIntrinsicContentSize" and when I add items - the height of the tableview changes, pushing the custom view below it further down.
Once enough items are added I the bottom custom view is hidden and the scroll doesn't work.
important fact - the bottom custom view doesn't have a bottom constraint. It is pushed down by the it's top constraint to the tableView.
If I do set a bottom constraint - the table view will no longer be dynamically resized.
The intended behaviour:
When a user adds items to the list and the list gets too big the ContentView will be scrollable so the user can scroll to see the bottom view.
The actual behaviour:
When a user adds items to the list and the list gets too big, the bottom view is pushed down and outside of sight and content is not scrollable.
What is happening and how can I fix it?
Below is what I think what is happening.
Since you are using UITableView, it has its own scroll view. So when the UITableView list gets too big, UITableView itself becomes scrollable rather than ScrollView's contentView becoming scrollable.
To achieve what you need, you would have to make the UITableView not scrollable and use the intrinsicHeight of the UITableView to get the actual height of UITableView along with all the items. If you have items with varying heights, it will be a problem because you won't know the height before rendering. With same height for all the rows, you can get the total height of the UITableView and set the height constraint to that value. This will increase the contentSize of the outer ScrollView, making it scrollable.
Apart from UITableView, you can also use UIStackView. This is because you are not using the reusing capabilities of UITableView anyways. Managing the datasource and delegates should not be a big problem.
You can create a constraint for tableview height, And take its reference to your swift file, by dragging it as you take other views. Now in your code, Just do this
tableViewHeightConstraint.constant = tableViewNoOfItems * tableViewCellHeight;
if you have set other constraints perfectly inside scrollview, It should work perfectly. Means TableView should have top, bottom, left, right margined constraints from the ScrollView.
try this code
tblViewHeight.constant = CGFloat( tableview row count * 45 )
var size = contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
if size.height < scrollView.frame.size.height
{
size = scrollView.frame.size
}
contenViewHeight.constant = size.height - scrollView.frame.size.height
scrollView.contentSize.height = contenViewHeight.constant
What I think you could do is:
Disable tableView's scroll tableView.isScrollEnabled = false
Every time a user adds items to the list, reload the tableView
Also using UIStackView with vertical axis and .fillEqually distribution as a Content View would be much more convenient as you won't need to set any positional constraints to your views, but may need to set height constraints if intrinsic content size can't be determined by the engine

Update constraints of other elements when UITableView height change

I have a UITableView of which I am defining height as 200 in autolayout. Based on that I am laying out other elements below it like UITextField etc. After that in run time I am fetching data from server and populating in UITableView due to which i am updating UITableView's height based on its content size. Following code I am using for it
self.myTableView.frame = CGRectMake(0 , 0, self.myTableView.frame.width, self.myTableView.contentSize.height)
But due to this, all the elements placed below UITableView still appear at same location which they were while laying out in Autolayout. Means change in height of UITableView makes no difference to them. Following image depicts this problem. What could be possible solution for this?
Here you can see, text fields are getting overlapped on tableview at run time. I am using Swift 2 in Xcode 7.2
If you have all required constraints to your table view and other view.
Don't change the frame of TableView to change height of it.
Instead create IBOutlet of height constraint of your tablview.
e.g. say IBOutlet name is constraintTableViewHeight,
then you can the the hight easily.
constraintTableViewHeight.constant = yourNewHeightValue
//update all constraint of your view and its inner view
self.view.layoutIfNeeded();
Refer Image to create IBOutlet for your height Constraint.
Take IBOutlet of NSLayoutConstraint for tableView Height and set its value not set tableview frame it's not working if you are already given constraints for tableview so change tableview hight constant
like if you take tblHeight for tableView height then set tblHeight.constant = self.myTableView.contentSize.height
You have to create an outlet connection for your table view height constraint:
#IBOutlet var heightConstraint: NSLayoutConstraint!
and when you want to change table view height, you can change the constraint value like this:
heightConstraint.constant = newHeightValue
self.view.layoutIfNeeded()

UITableViewCells scroll over header when scrolling down

I'm trying to figure out what my options are for having the top of a UITableView be able to stay at rest about 1/3 down the screen but still be able to scroll over the top of an image above it.
I was thinking UITableView on a UIScrollView but I haven't seen this exact thing while googling around for it.
Basically I'm looking to have a header and then the table view will scroll over the top of the header without the header moving. If I set the table view below a simple uiview or imageview, it will always be below it.
Any suggestions?
Add your header as a separate view below your table view. Your table view should completely overlap the underlying view, as if it would obscure it. Then set the table view's tableHeaderView to a transparent UIView, through which you will be able to see your underlying header view. Your table view's background colour should also be clear.
CGRect headerFrame = self.underlyingHeaderView.bounds;
UIView *transparentView = [UIView alloc] initWithFrame:headerFrame];
transparentView.backgroundColor = [UIColor clearColor];
self.tableView.tableHeaderView = transparentView;

Is it possible to subclass UITableView and make it have an intrinsic content size for its height based on the number of cells?

I want my UITableView to only be as tall as it needs to be, as it floats above the main UIViewController's view. If I hardcode a height of 200 and theres' only one cell in the table, it looks silly.
I'm aware in my view controller I could monitor the table view and define the height of it based on the number of cells it has, but the height is a property of the view, and for MVC it doesn't make much sense for the controller to be actively managing a view's height.
Is it possible to have a UITableView subclass, and have it define an intrinsic height based on the number of cells it holds? So with Auto Layout I could add the subclass to my view, specify its width, center it vertically, and perhaps define a "less than or equal" height constraint saying to keep it smaller than 200pts. But for the most part have the intrinsic content size of the view define the height of the view automatically?
This would be just like a UILabel being able to be centered horizontally and vertically with some distance from the left and right, and have it grow and shrink vertically automatically.
Could I feasibly do this with a UITableView subclass?
You can do this easily by having the table view use its contentSize property to "know" how tall it needs to be. This value could be somewhat inaccurate if you're using estimated row heights, but it should be good enough. In this example, I gave the table view a height constraint (as well as width and centerY), and made an IBOutlet to it (heightCon). The only code needed was this,
#interface RDTableView ()
#property (weak,nonatomic) IBOutlet NSLayoutConstraint *heightCon;
#end
#implementation RDTableView
-(void)reloadData {
[super reloadData];
self.heightCon.constant = MIN(200, self.contentSize.height);
}
You would also have to override reloadRowsAtIndexPaths:withRowAnimation: and
reloadSections:withRowAnimation: if you're updating your table with either of those as well.
This feels like a chicken/egg problem you have with designing your UI. Sounds like you want to set the height of the floating tableview based on how many rows it contains * height per row, but the tableview doesn't know any of this information until its been drawn, ie [tableView reloadData] and the corresponding height for row delegate methods are called.
I'd suggest rendering the tableview offscreen somewhere, draw all the rows, sum up all the heights for each row, then present the view to the user with the appropriate CGRect.
When you (re)load data in your TableView, you can do this. The TableView will take only the height it needs
CGRect frame = youTableView;
frame.size.height = CELL_HEIGHT*[yourArray count];
if(frame.size.height > MAX_TABLEVIEW_HEIGHT)
{
frame.size.height = MAX_TABLEVIEW_HEIGHT
}
youTableView.frame = frame;

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.

Resources